From 1dd2c1cb915a9b807ccb9425d0e61ac0d4a1bf6d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 19 Jun 2019 14:30:44 +0200 Subject: [PATCH 01/35] Revert "Revert "U4 10147 - Bypass User Security option in pickers (#2441)"" This reverts commit 994c6eed --- .../tree/umbtreesearchbox.directive.js | 7 + .../src/common/resources/content.resource.js | 1577 +++++++++-------- .../src/common/resources/entity.resource.js | 49 +- .../src/common/resources/media.resource.js | 6 +- .../src/common/services/search.service.js | 22 +- .../overlays/contentpicker/contentpicker.html | 3 +- .../linkpicker/linkpicker.controller.js | 301 ++-- .../overlays/linkpicker/linkpicker.html | 2 + .../mediaPicker/mediapicker.controller.js | 55 +- .../treepicker/treepicker.controller.js | 1047 +++++------ .../overlays/treepicker/treepicker.html | 1 + .../contentpicker/contentpicker.controller.js | 4 +- .../grid/editors/media.controller.js | 17 +- .../grid/editors/rte.controller.js | 17 +- .../mediapicker/mediapicker.controller.js | 16 +- .../multiurlpicker.controller.js | 3 +- .../relatedlinks/relatedlinks.controller.js | 2 + .../propertyeditors/rte/rte.controller.js | 15 +- .../propertyeditors/rte/rte.prevalues.html | 1 + src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/EntityController.cs | 106 +- src/Umbraco.Web/Editors/MediaController.cs | 14 +- .../ContentPicker2PropertyEditor.cs | 14 +- .../PropertyEditors/GridPropertyEditor.cs | 3 + .../MediaPicker2PropertyEditor.cs | 9 +- .../MultiNodeTreePicker2PropertyEditor.cs | 10 +- .../MultiUrlPickerPropertyEditor.cs | 7 + .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 8 + src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 34 +- .../Trees/ContentTreeControllerBase.cs | 12 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 10 + .../Trees/TreeQueryStringParameters.cs | 3 +- ...EnsureUserPermissionForContentAttribute.cs | 15 +- .../FilterAllowedOutgoingMediaAttribute.cs | 9 +- 35 files changed, 1861 insertions(+), 1556 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 4ba4cf96bb..b81e62a66b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", + ignoreUserStartNodes: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) { scope.showSearch = "false"; } + //used to cancel any request in progress if another one needs to take it's place var canceler = null; @@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } + //append ignoreUserStartNodes value if there is one + if (scope.ignoreUserStartNodes) { + searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; + } + searcher(searchArgs).then(function (data) { scope.searchCallback(data); //set back to null so it can be re-created diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index b695809eaa..77e587bcc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1,782 +1,795 @@ -/** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
-  *          });    
-  * 
- **/ - -function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { - return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, - - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * contentResource.sort({ parentId: 1244, sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * contentResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
-          *      alert("node didnt move:" + err.data.Message); 
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
-          * contentResource.copy({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was copied");
-          *    }, function(err){
-          *      alert("node wasnt copy:" + err.data.Message); 
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
-          * contentResource.unPublish(1234)
-          *    .then(function() {
-          *        alert("node was unpulished");
-          *    }, function(err){
-          *      alert("node wasnt unpublished:" + err.data.Message); 
-          *    });
-          * 
- * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
-          * contentResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
-          * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
-          * contentResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
-          * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *        var myDoc = content; 
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - getNotifySettingsById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNotificationOptions", - [{ contentId: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - setNotifySettingsById: function (id, options) { - if (!id) { - throw "contentId cannot be null"; - } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostNotificationOptions", - { contentId: id, notifyOptions: options })), - 'Failed to set notify settings for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
-          * contentResource.getByIds( [1234,2526,28262])
-          *    .then(function(contentArray) {
-          *        var myDoc = contentArray; 
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
-          * contentResource.getScaffold(1234, 'homepage')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
-          *        myDoc.name = "My new document"; 
-          *
-          *        contentResource.publish(myDoc, true)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
-          * 
- * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - getBlueprintScaffold: function (parentId, blueprintId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId}])), - 'Failed to retrieve blueprint for id ' + blueprintId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
-          * contentResource.getNiceUrl(id)
-          *    .then(function(url) {
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
-          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray; 
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - includeProperties: [], - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - { - id: parentId, - includeProperties: _.pluck(options.includeProperties, 'alias').join(","), - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - orderBySystemField: toBool(options.orderBySystemField), - filter: options.filter - })), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
-          * contentResource.hasPermission('p',1234)
-          *    .then(function() {
-          *        alert('You are allowed to publish this item');
-          *    });
-          * 
- * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name!";
-          *          contentResource.save(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.publish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.sendToPublish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and notication send off");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-          * contentResource.publishById(1234)
-          *    .then(function(content) {
-          *        alert("published");
-          *    });
-          * 
- * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - }, - - createBlueprintFromContent: function (contentId, name) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { - contentId: contentId, name: name - }) - ), - "Failed to create blueprint from content with id " + contentId - ); - } - - - }; -} - -angular.module('umbraco.resources').factory('contentResource', contentResource); +/** + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
+  *    contentResource.getById(1234)
+  *          .then(function(data) {
+  *              $scope.content = data;
+  *          });    
+  * 
+ **/ + +function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files, restApiUrl) { + return umbRequestHelper.postSaveContent({ + restApiUrl: restApiUrl, + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } + + return { + + + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, + + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+          * var ids = [123,34533,2334,23434];
+          * contentResource.sort({ parentId: 1244, sortedIds: ids })
+          *    .then(function() {
+          *        $scope.complete = true;
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+          * contentResource.move({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was moved");
+          *    }, function(err){
+          *      alert("node didnt move:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+          * contentResource.copy({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was copied");
+          *    }, function(err){
+          *      alert("node wasnt copy:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
+          * contentResource.unPublish(1234)
+          *    .then(function() {
+          *        alert("node was unpulished");
+          *    }, function(err){
+          *      alert("node wasnt unpublished:" + err.data.Message); 
+          *    });
+          * 
+ * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
+          * contentResource.emptyRecycleBin()
+          *    .then(function() {
+          *        alert('its empty!');
+          *    });
+          * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
+          * contentResource.deleteById(1234)
+          *    .then(function() {
+          *        alert('its gone!');
+          *    });
+          * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + deleteBlueprint: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *        var myDoc = content; 
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id id of content item to return + * @param {Object} options optional options object + * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id, options) { + var defaults = { + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + 'Failed to retrieve data for content id ' + id); + }, + + getBlueprintById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + getNotifySettingsById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNotificationOptions", + [{ contentId: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + setNotifySettingsById: function (id, options) { + if (!id) { + throw "contentId cannot be null"; + } + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostNotificationOptions", + { contentId: id, notifyOptions: options })), + 'Failed to set notify settings for content id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
+          * contentResource.getByIds( [1234,2526,28262])
+          *    .then(function(contentArray) {
+          *        var myDoc = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+          * contentResource.getScaffold(1234, 'homepage')
+          *    .then(function(scaffold) {
+          *        var myDoc = scaffold;
+          *        myDoc.name = "My new document"; 
+          *
+          *        contentResource.publish(myDoc, true)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and published again");
+          *            });
+          *    });
+          * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + getBlueprintScaffold: function (parentId, blueprintId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), + 'Failed to retrieve blueprint for id ' + blueprintId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
+          * contentResource.getNiceUrl(id)
+          *    .then(function(url) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
+          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + includeProperties: [], + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + { + id: parentId, + includeProperties: _.pluck(options.includeProperties, 'alias').join(","), + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + orderBySystemField: toBool(options.orderBySystemField), + filter: options.filter + })), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
+          * contentResource.hasPermission('p',1234)
+          *    .then(function() {
+          *        alert('You are allowed to publish this item');
+          *    });
+          * 
+ * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, + + getDetailedPermissions: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); + }, + + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name!";
+          *          contentResource.save(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + saveBlueprint: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name, and be published!";
+          *          contentResource.publish(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and published again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
+          * contentResource.getById(1234)
+          *    .then(function(content) {
+          *          content.name = "I want a new name, and be published!";
+          *          contentResource.sendToPublish(content, false)
+          *            .then(function(content){
+          *                alert("Retrieved, updated and notication send off");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
+          * contentResource.publishById(1234)
+          *    .then(function(content) {
+          *        alert("published");
+          *    });
+          * 
+ * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + }, + + createBlueprintFromContent: function (contentId, name) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { + contentId: contentId, name: name + }) + ), + "Failed to create blueprint from content with id " + contentId + ); + } + + + }; +} + +angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 72f8ad5539..4875491dc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -292,14 +292,29 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type) { + getAncestors: function (id, type, options) { + var defaults = { + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{id: id}, {type: type}])), - 'Failed to retrieve ancestor data for id ' + id); + [ + { id: id }, + { type: type }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } + ])), + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -431,7 +446,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -460,7 +476,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + ignoreUserStartNodes: options.ignoreUserStartNodes } )), 'Failed to retrieve child data for id ' + parentId); @@ -488,12 +505,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler) { + search: function (query, type, options, canceler) { - var args = [{ query: query }, { type: type }]; - if (searchFrom) { - args.push({ searchFrom: searchFrom }); + var defaults = { + searchFrom: null, + ignoreUserStartNodes: false + }; + if (options === undefined) { + options = {}; } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; var httpConfig = {}; if (canceler) { @@ -505,7 +529,12 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "Search", - args), + { + query: query, + type: type, + searchFrom: options.searchFrom, + ignoreUserStartNodes: options.ignoreUserStartNodes + }), httpConfig), 'Failed to retrieve entity data for query ' + query); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 8c27f20aea..e968913047 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -329,7 +329,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true + orderBySystemField: true, + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -372,7 +373,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } + { filter: options.filter }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } ])), 'Failed to retrieve children for media item ' + parentId); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 8738c1011e..0d00678282 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,7 +42,11 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -67,7 +71,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -92,7 +101,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Media", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); @@ -157,4 +171,4 @@ angular.module('umbraco.services') var currentSection = sectionAlias; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 43eab532d4..4391e50c28 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,6 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="content"> @@ -45,4 +46,4 @@ on-close="closeMiniListView()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index fcce34621b..79b9362d3f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,57 +1,58 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.model; + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.model; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); - } + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); + } - $scope.dialogTreeEventHandler = $({}); - $scope.model.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + $scope.dialogTreeEventHandler = $({}); + $scope.model.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + }; + $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.showTarget = $scope.model.hideTarget !== true; - $scope.showTarget = $scope.model.hideTarget !== true; + if (dialogOptions.currentTarget) { + // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target + $scope.model.target = angular.copy(dialogOptions.currentTarget); + //if we have a node ID, we fetch the current node to build the form data + if ($scope.model.target.id || $scope.model.target.udi) { - if (dialogOptions.currentTarget) { - // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target - $scope.model.target = angular.copy(dialogOptions.currentTarget); - //if we have a node ID, we fetch the current node to build the form data - if ($scope.model.target.id || $scope.model.target.udi) { + //will be either a udi or an int + var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; - //will be either a udi or an int - var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; + // is it a content link? + if (!$scope.model.target.isMedia) { + // get the content path + entityResource.getPath(id, "Document").then(function (path) { + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: path, + tree: "content" + }); + }); - // is it a content link? - if (!$scope.model.target.isMedia) { - // get the content path - entityResource.getPath(id, "Document").then(function(path) { - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: path, - tree: "content" - }); - }); - - // get the content properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } - } else if ($scope.model.target.url.length) { - // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } + } else if ($scope.model.target.url.length) { + // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/); if (indexOfAnchor > -1) { @@ -60,124 +61,132 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", // then rewrite the model and populate the link $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor); } - } - } else if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } + } + } else if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } - function nodeSelectHandler(ev, args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - eventsService.emit("dialogs.linkPicker.select", args); + eventsService.emit("dialogs.linkPicker.select", args); - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; - $scope.model.target.udi = args.node.udi; - $scope.model.target.name = args.node.name; + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.udi = args.node.udi; + $scope.model.target.name = args.node.name; - if (args.node.id < 0) { - $scope.model.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } + if (args.node.id < 0) { + $scope.model.target.url = "/"; + } else { + contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } - if (!angular.isUndefined($scope.model.target.isMedia)) { - delete $scope.model.target.isMedia; - } - } + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } - function nodeExpandedHandler(ev, args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, - show: true, - submit: function (model) { - var media = model.selectedImages[0]; + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + if (dialogOptions.ignoreUserStartNodes) { + startNodeId = -1; + startNodeIsVirtual = true; + } + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + show: true, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + submit: function (model) { + var media = model.selectedImages[0]; - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - // make sure the content tree has nothing highlighted - $scope.dialogTreeEventHandler.syncTree({ - path: "-1", - tree: "content" - }); - } - }; - }); - }; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } + // make sure the content tree has nothing highlighted + $scope.dialogTreeEventHandler.syncTree({ + path: "-1", + tree: "content" + }); + } + }; + }); + }; - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { - node: node - }); - }; + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { + node: node + }); + }; - function openMiniListView(node) { - $scope.miniListView = node; - } + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; - }); + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index deba19aa11..c9f3ad54a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -50,6 +50,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> @@ -64,6 +65,7 @@ 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { + entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) + .then(function(anc) { $scope.path = _.filter(anc, function(f) { return f.path.indexOf($scope.startNodeId) !== -1; @@ -161,13 +168,13 @@ angular.module("umbraco") } else { $scope.path = []; } - + mediaTypeHelper.getAllowedImagetypes(folder.id) .then(function (types) { $scope.acceptedMediatypes = types; }); - - $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); @@ -263,6 +270,17 @@ angular.module("umbraco") } } + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + function gotoStartNode(err) { $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } @@ -297,7 +315,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: '', + ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }; getChildren($scope.currentFolder.id); } @@ -369,7 +388,7 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id) + return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) .then(function(data) { $scope.searchOptions.filter = ""; $scope.images = data.items ? data.items : []; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 615f86b384..e1ce332b48 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,517 +1,530 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { - - var tree = null; - var dialogOptions = $scope.model; - $scope.treeReady = false; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; - // if you need to load a not initialized tree set this value to false - default is true - $scope.onlyInitialized = dialogOptions.onlyInitialized; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - $scope.model.selection = []; - - //Used for toggling an empty-state message - //Some trees can have no items (dictionary & forms email templates) - $scope.hasItems = true; - $scope.emptyStateMessage = dialogOptions.emptyStateMessage; - var node = dialogOptions.currentNode; - - //This is called from ng-init - //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. - //this is probably an anti pattern IMO and shouldn't be used - $scope.init = function (contentType) { - - if (contentType === "content") { - $scope.entityType = "Document"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); - } - } else if (contentType === "member") { - $scope.entityType = "Member"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); - } - } else if (contentType === "media") { - $scope.entityType = "Media"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - } - } - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - $scope.entityType = "Member"; - } - else if (dialogOptions.section === "media") { - $scope.entityType = "Media"; - } - - // Search and listviews is only working for content, media and member section - var searchableSections = ["content", "media", "member"]; - - $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; - - //if a alternative startnode is used, we need to check if it is a container - if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { - entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { - if (node.metaData.IsContainer) { - openMiniListView(node); - } - initTree(); - }); - } - else { - initTree(); - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filterTypes = dialogOptions.filter.substring(1); - } else { - dialogOptions.filterExclude = false; - dialogOptions.filterTypes = dialogOptions.filter; - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - - $scope.filter = { - filterAdvanced: dialogOptions.filterAdvanced, - filterExclude: dialogOptions.filterExclude, - filter: dialogOptions.filterTypes - }; - } - - function initTree() { - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - $scope.treeReady = true; - } - - function nodeExpandedHandler(ev, args) { - - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - //args.tree contains children (args.tree.root.children) - $scope.hasItems = args.tree.root.children.length > 0; - - tree = args.tree; - - var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; - var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; - if (startNodeNotDefined && nodeHasPath) { - $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); - } - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function (child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - if ($scope.model.select) { - $scope.model.select(args.node) - } else { - select(args.node.name, args.node.id); - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - - var rootNode = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - - if ($scope.multiPicker) { - if (entity) { - multiSelectItem(entity); - } else { - multiSelectItem(rootNode); - } - } - else { - $scope.model.selection.push(rootNode); - $scope.model.submit($scope.model); - } - } - else { - - if ($scope.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.model.selection.push(entity); - $scope.model.submit($scope.model); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - $scope.model.selection.push(ent); - $scope.model.submit($scope.model); - }); - } - } - } - } - - function multiSelectItem(item) { - - var found = false; - var foundIndex = 0; - - if ($scope.model.selection.length > 0) { - for (i = 0; $scope.model.selection.length > i; i++) { - var selectedItem = $scope.model.selection[i]; - if (selectedItem.id === item.id) { - found = true; - foundIndex = i; - } - } - } - - if (found) { - $scope.model.selection.splice(foundIndex, 1); - } else { - $scope.model.selection.push(item); - } - - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function (n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, $scope.entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function (c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function (c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function (c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function (results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function (item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.model.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - - $scope.selectListViewNode = function (node) { - select(node.name, node.id); - //toggle checked state - node.selected = node.selected === true ? false : true; - }; - - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; - - function openMiniListView(node) { - $scope.miniListView = node; - } - - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", + function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { + + var tree = null; + var dialogOptions = $scope.model; + $scope.treeReady = false; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; + // if you need to load a not initialized tree set this value to false - default is true + $scope.onlyInitialized = dialogOptions.onlyInitialized; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + } + + $scope.model.selection = []; + + //Used for toggling an empty-state message + //Some trees can have no items (dictionary & forms email templates) + $scope.hasItems = true; + $scope.emptyStateMessage = dialogOptions.emptyStateMessage; + var node = dialogOptions.currentNode; + + //This is called from ng-init + //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. + //this is probably an anti pattern IMO and shouldn't be used + $scope.init = function (contentType) { + + if (contentType === "content") { + $scope.entityType = "Document"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); + } + } else if (contentType === "member") { + $scope.entityType = "Member"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); + } + } else if (contentType === "media") { + $scope.entityType = "Media"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + } + } + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + $scope.entityType = "Member"; + } + else if (dialogOptions.section === "media") { + $scope.entityType = "Media"; + } + + // Search and listviews is only working for content, media and member section + var searchableSections = ["content", "media", "member"]; + + $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; + + //if a alternative startnode is used, we need to check if it is a container + if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { + entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + initTree(); + }); + } + else { + initTree(); + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filterTypes = dialogOptions.filter.substring(1); + } else { + dialogOptions.filterExclude = false; + dialogOptions.filterTypes = dialogOptions.filter; + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + + $scope.filter = { + filterAdvanced: dialogOptions.filterAdvanced, + filterExclude: dialogOptions.filterExclude, + filter: dialogOptions.filterTypes + }; + } + + function initTree() { + //create the custom query string param for this tree + var params = []; + + if (dialogOptions.startNodeId) + params.push("startNodeId=" + dialogOptions.startNodeId); + + if (dialogOptions.ignoreUserStartNodes) + params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + + if (dialogOptions.customTreeParams) + params.push(dialogOptions.customTreeParams); + + $scope.customTreeParams = params.join('&'); + + $scope.treeReady = true; + } + + function nodeExpandedHandler(ev, args) { + + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + //args.tree contains children (args.tree.root.children) + $scope.hasItems = args.tree.root.children.length > 0; + + tree = args.tree; + + var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; + var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; + if (startNodeNotDefined && nodeHasPath) { + $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); + } + + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function (child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + if ($scope.model.select) { + $scope.model.select(args.node) + } else { + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + + var rootNode = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + + if ($scope.multiPicker) { + if (entity) { + multiSelectItem(entity); + } else { + multiSelectItem(rootNode); + } + } + else { + $scope.model.selection.push(rootNode); + $scope.model.submit($scope.model); + } + } + else { + + if ($scope.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + + function multiSelectItem(item) { + + var found = false; + var foundIndex = 0; + + if ($scope.model.selection.length > 0) { + for (i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if (selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + + if (found) { + $scope.model.selection.splice(foundIndex, 1); + } else { + $scope.model.selection.push(item); + } + + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, $scope.entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function (c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function (results) { + + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function (item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.model.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + + $scope.selectListViewNode = function (node) { + select(node.name, node.id); + //toggle checked state + node.selected = node.selected === true ? false : true; + }; + + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index a3a5fd6107..c338e3402c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -10,6 +10,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ee8c193b4b..ce836a8d68 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -66,6 +66,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, + ignoreUserStartNodes: false, maxNumber: 1, minNumber : 0, startNode: { @@ -99,7 +100,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - + $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 21f9534848..a093ccb034 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,12 +1,20 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { + + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } $scope.setImage = function(){ @@ -14,6 +22,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; + $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 397438d5a0..cf81300b32 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -16,6 +16,7 @@ view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -26,11 +27,23 @@ } function openMediaPicker(editor, currentTarget, userData) { + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } + vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + showDetails: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 7b8445a4dc..1f1305b0f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -7,12 +7,19 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function(userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } function setupViewModel() { @@ -105,6 +112,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl title: "Select media", startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index e4f2ae303f..c7e67a0a42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,10 +67,11 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function (model) { if (model.target.url || model.target.anchor) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 142298e0d8..6047169c54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -25,6 +25,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { @@ -50,6 +51,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 1fc648083e..f8dea2ee8b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -274,6 +274,7 @@ angular.module("umbraco") view: "linkpicker", currentTarget: currentTarget, anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -285,14 +286,24 @@ angular.module("umbraco") //Create the insert media plugin tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } $scope.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index b9d63df9f3..9d7ff0cf40 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -40,4 +40,5 @@ Pixels + \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index beff2c2615..b8d9c8bcb6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -262,10 +262,11 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id /// /// + /// If set to true, user and group start node permissions will be ignored. /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) + public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -1116,6 +1117,7 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against + /// If set to true, user and group start node permissions will be ignored. /// internal static bool CheckPermissions( IDictionary storage, @@ -1125,7 +1127,8 @@ namespace Umbraco.Web.Editors IEntityService entityService, int nodeId, char[] permissionsToCheck = null, - IContent contentItem = null) + IContent contentItem = null, + bool ignoreUserStartNodes = false) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -1146,6 +1149,11 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + if(ignoreUserStartNodes) + { + return true; + } + var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 8bde435ef6..a3f76db4f2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -83,8 +83,25 @@ namespace Umbraco.Web.Editors /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set to true, user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) { //TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -92,7 +109,7 @@ namespace Umbraco.Web.Editors if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); - return ExamineSearch(query, type, searchFrom); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); } /// @@ -527,6 +544,7 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -535,6 +553,20 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + false, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + bool ignoreUserStartNodes, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -562,7 +594,7 @@ namespace Umbraco.Web.Editors break; } - entities = aids == null || aids.Contains(Constants.System.Root) + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); } @@ -598,9 +630,15 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) { - return GetResultForAncestors(id, type); + return GetResultForAncestors(id, type, false); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) + { + return GetResultForAncestors(id, type, ignoreUserStartNodes); } public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) @@ -614,11 +652,12 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom); + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); } @@ -645,7 +684,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -654,35 +693,38 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - int[] aids = null; - switch (entityType) + if (ignoreUserStartNodes == false) { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) + int[] aids = null; + switch (entityType) { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); } - ids = lids.ToArray(); } return ids.Length == 0 diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index d411bf2197..6c0c293572 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -264,11 +264,12 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) { if (pageNumber > 0) return new PagedResult>(0, 0, 0); @@ -346,6 +347,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, @@ -360,7 +362,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } throw new HttpResponseException(HttpStatusCode.NotFound); } @@ -400,6 +402,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, @@ -417,7 +420,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } } @@ -433,7 +436,8 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { foreach (var type in new[] { typeof(int), typeof(Guid) }) { diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 5b99264113..0039385e5f 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,10 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -39,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() { Key = "showOpenButton", @@ -48,6 +49,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Opens the node in a dialog" }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "treepicker", @@ -60,4 +68,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index af30b4ceeb..4a8803a099 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -136,6 +136,9 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } + + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } #region Application event handler, used to bind to events on startup diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 2d9ee68b3b..6aa7ab4a54 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -57,6 +57,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Do not allow folders to be picked." }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "mediapicker", @@ -69,4 +76,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f43ecd48be..f57a9951b6 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -38,6 +39,13 @@ namespace Umbraco.Web.PropertyEditors { //create the fields Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNode", View = "treesource", @@ -118,4 +126,4 @@ namespace Umbraco.Web.PropertyEditors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index a058a16b8a..f3ae317efa 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -32,6 +32,13 @@ namespace Umbraco.Web.PropertyEditors { public MultiUrlPickerPreValueEditor() { + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); Fields.Add(new PreValueField { Key = "minNumber", diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index 541dccaa4e..a96c0724ff 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -32,8 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } + [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index cd08a8ad45..69445bc304 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -23,6 +23,14 @@ namespace Umbraco.Web.PropertyEditors Key = "editor" }); + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Name = "Hide Label", diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 38347de9ed..8845e9c323 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.Search internal class UmbracoTreeSearcher { /// + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Searches for results based on the entity type /// /// @@ -28,12 +29,37 @@ namespace Umbraco.Web.Search /// /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable ExamineSearch( UmbracoHelper umbracoHelper, string query, UmbracoEntityTypes entityType, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return ExamineSearch(umbracoHelper, query, entityType, pageSize, pageIndex, out totalFound, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + public IEnumerable ExamineSearch( + UmbracoHelper umbracoHelper, + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, out long totalFound, bool ignoreUserStartNodes, string searchFrom = null) { var sb = new StringBuilder(); @@ -61,12 +87,12 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; case UmbracoEntityTypes.Document: type = "content"; var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -203,7 +229,7 @@ namespace Umbraco.Web.Search } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService) { if (sb == null) throw new ArgumentNullException("sb"); if (entityService == null) throw new ArgumentNullException("entityService"); @@ -228,7 +254,7 @@ namespace Umbraco.Web.Search // make sure we don't find anything sb.Append("+__Path:none "); } - else if (startNodeIds.Contains(-1) == false) // -1 = no restriction + else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction { var entityPaths = entityService.GetAllPaths(objectType, startNodeIds); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index af38e21546..2624c89b56 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { node.AdditionalData["noAccess"] = true; } @@ -91,7 +91,7 @@ namespace Umbraco.Web.Trees { bool hasPathAccess; var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); - if (entityIsAncestorOfStartNodes == false) + if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); @@ -101,7 +101,7 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (hasPathAccess == false) + if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } @@ -141,7 +141,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (HasPathAccess(id, queryStrings) == false) + if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) { LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; @@ -158,7 +158,7 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id).ToList(); + var entities = GetChildEntities(id, queryStrings).ToList(); nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, @@ -190,7 +190,7 @@ namespace Umbraco.Web.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected IEnumerable GetChildEntities(string id) + protected IEnumerable GetChildEntities(string id, FormDataCollection queryStrings) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 98eb2ec8bc..2ea41ff128 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -349,6 +349,16 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); } + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); + } + /// /// An event that allows developers to modify the tree node collection that is being rendered /// diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 7f6fa28187..c79b9f8781 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,7 +8,8 @@ public const string IsDialog = "isDialog"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 18880b9f96..64fe9a4b65 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; @@ -52,7 +52,7 @@ namespace Umbraco.Web.WebApi.Filters _paramName = paramName; _permissionToCheck = ActionBrowse.Instance.Letter; - } + } public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) : this(paramName) @@ -72,6 +72,9 @@ namespace Umbraco.Web.WebApi.Filters //not logged in throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } + + var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && + bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); int nodeId; if (_nodeId.HasValue == false) @@ -124,9 +127,11 @@ namespace Umbraco.Web.WebApi.Filters actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index beb67f3395..23d4fb871c 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,11 +3,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { @@ -77,7 +79,12 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - FilterBasedOnStartNode(items, user); + bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + if (ignoreUserStartNodes == false) + { + FilterBasedOnStartNode(items, user); + } } internal void FilterBasedOnStartNode(IList items, IUser user) From 4e4487167126d89dc2174592d529dc73b5f9828b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 20 Jun 2019 15:57:31 +0200 Subject: [PATCH 02/35] The NuGet packages needs an extra DotNetCompilerPlatform transform to be sure it gets applied as it sometimes didn't Added SignalR binding redirect for Umbraco Cloud Added System.Web.Http.WebHost binding redirect for uCommerce --- build/NuSpecs/tools/Web.config.install.xdt | 18 ++++++++++++++++-- src/Umbraco.Web.UI/web.Template.config | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index a381a58c3e..4f8a1927a8 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -324,7 +324,6 @@ - @@ -337,6 +336,9 @@ + + + @@ -369,7 +371,7 @@ - + @@ -398,6 +400,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index fe25dd0c0f..583952f29d 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -466,6 +466,14 @@ + + + + + + + + From 4df838c20c5f128d429602f4f933f851469dfe0c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 20 Jun 2019 16:02:42 +0200 Subject: [PATCH 03/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1358 - Changed the ignoreUserStartNodes in public endpoints to the data type id, and then look up the setting --- src/Umbraco.Core/Services/DataTypeService.cs | 1296 ++++----- src/Umbraco.Core/Services/IDataTypeService.cs | 335 +-- .../tree/umbtreesearchbox.directive.js | 10 +- .../src/common/resources/content.resource.js | 122 +- .../src/common/resources/entity.resource.js | 1168 ++++---- .../src/common/resources/media.resource.js | 1118 ++++---- .../src/common/services/search.service.js | 338 +-- .../common/dialogs/linkpicker.controller.js | 334 +-- .../src/views/common/dialogs/linkpicker.html | 173 +- .../common/dialogs/treepicker.controller.js | 861 +++--- .../overlays/contentpicker/contentpicker.html | 2 +- .../linkpicker/linkpicker.controller.js | 12 +- .../overlays/linkpicker/linkpicker.html | 3 +- .../mediaPicker/mediapicker.controller.js | 852 +++--- .../treepicker/treepicker.controller.js | 6 +- .../overlays/treepicker/treepicker.html | 112 +- .../contentpicker/contentpicker.controller.js | 732 ++--- .../grid/editors/media.controller.js | 6 +- .../grid/editors/rte.controller.js | 12 +- .../mediapicker/mediapicker.controller.js | 369 +-- .../multiurlpicker.controller.js | 5 +- .../relatedlinks/relatedlinks.controller.js | 484 ++-- .../propertyeditors/rte/rte.controller.js | 805 +++--- src/Umbraco.Web/Editors/ContentController.cs | 2454 ++++++++--------- src/Umbraco.Web/Editors/EntityController.cs | 2003 +++++++------- src/Umbraco.Web/Editors/MediaController.cs | 2031 +++++++------- .../ContentEditing/ContentPropertyBasic.cs | 97 +- .../Mapping/ContentPropertyBasicConverter.cs | 153 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 808 +++--- .../Trees/TreeQueryStringParameters.cs | 30 +- ...EnsureUserPermissionForContentAttribute.cs | 302 +- .../FilterAllowedOutgoingMediaAttribute.cs | 11 +- 32 files changed, 8547 insertions(+), 8497 deletions(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 0ee3e9b823..43c2b69c47 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -1,640 +1,656 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.PropertyEditors; -using umbraco.interfaces; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the DataType Service, which is an easy access to operations involving - /// - public class DataTypeService : ScopeRepositoryService, IDataTypeService - { - - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - } - - #region Containers - - public Attempt> CreateContainer(int parentId, string name, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - - try - { - var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) - { - Name = name, - ParentId = parentId, - CreatorId = userId - }; - - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.AddOrUpdate(container); - uow.Commit(); - - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - //TODO: Audit trail ? - - return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); - } - catch (Exception ex) - { - return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); - } - } - } - - public EntityContainer GetContainer(int containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public EntityContainer GetContainer(Guid containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public IEnumerable GetContainers(string name, int level) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(name, level); - } - } - - public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) - { - var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - var asInt = x.TryConvertTo(); - if (asInt) return asInt.Result; - return int.MinValue; - }) - .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) - .ToArray(); - - return GetContainers(ancestorIds); - } - - public IEnumerable GetContainers(int[] containerIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.GetAll(containerIds); - } - } - - public Attempt SaveContainer(EntityContainer container, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - { - var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); - return OperationStatus.Exception(evtMsgs, ex); - } - - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return OperationStatus.Exception(evtMsgs, ex); - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - return OperationStatus.Cancelled(evtMsgs); - - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - repo.AddOrUpdate(container); - uow.Commit(); - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - } - - //TODO: Audit trail ? - - return OperationStatus.Success(evtMsgs); - } - - public Attempt DeleteContainer(int containerId, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); - - if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.Delete(container); - uow.Commit(); - - uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); - - return OperationStatus.Success(evtMsgs); - //TODO: Audit trail ? - } - } - - #endregion - - /// - /// Gets a by its Name - /// - /// Name of the - /// - public IDataTypeDefinition GetDataTypeDefinitionByName(string name) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// - public IDataTypeDefinition GetDataTypeDefinitionById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.Key == id); - - var definitions = repository.GetByQuery(query); - return definitions.FirstOrDefault(); - } - } - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// Collection of objects with a matching contorl id - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - public IEnumerable GetDataTypeDefinitionByControlId(Guid id) - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); - return GetDataTypeDefinitionByPropertyEditorAlias(alias); - } - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - public IEnumerable GetAllDataTypeDefinitions(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets all prevalues for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - public IEnumerable GetPreValuesByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - //now convert the collection to a string list - var collection = repository.GetPreValuesCollectionByDataTypeId(id); - //now convert the collection to a string list - return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); - } - } - - /// - /// Returns the PreValueCollection for the specified data type - /// - /// - /// - public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValuesCollectionByDataTypeId(id); - } - } - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - public string GetPreValueAsString(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValueAsString(id); - } - } - - public Attempt> Move(IDataTypeDefinition toMove, int parentId) - { - var evtMsgs = EventMessagesFactory.Get(); - - var moveInfo = new List>(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - try - { - EntityContainer container = null; - if (parentId > 0) - { - container = containerRepository.Get(parentId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - moveInfo.AddRange(repository.Move(toMove, container)); - } - catch (DataOperationException ex) - { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); - } - uow.Commit(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - uow.Events.Dispatch(Moved, this, moveEventArgs); - } - - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); - } - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) - { - throw new ArgumentException("Cannot save datatype with empty name."); - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - - Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - } - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - public void Save(IEnumerable dataTypeDefinitions, int userId = 0) - { - Save(dataTypeDefinitions, userId, true); - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); - if (raiseEvents) - { - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); - uow.Commit(); - } - } - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - public void SavePreValues(int dataTypeId, IEnumerable values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - uow.Commit(); - } - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(int dataTypeId, IDictionary values) - { - var dtd = GetDataTypeDefinitionById(dataTypeId); - if (dtd == null) - { - throw new InvalidOperationException("No data type found for id " + dataTypeId); - } - SavePreValues(dtd, values); - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - uow.Commit(); - } - } - - /// - /// This will save a data type and it's pre-values in one transaction - /// - /// - /// - /// - public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - // if preValues contain the data type, override the data type definition accordingly - if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) - dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - - //add/update the dtd - repository.AddOrUpdate(dataTypeDefinition); - - //add/update the prevalues - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - } - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Optional Id of the user issueing the deletion - public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - repository.Delete(dataTypeDefinition); - - Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(Deleted, this, deleteEventArgs); - } - } - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IDataType GetDataTypeById(Guid id) - { - return DataTypesResolver.Current.GetById(id); - } - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IEnumerable GetAllDataTypes() - { - return DataTypesResolver.Current.DataTypes; - } - - private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) - { - var auditRepo = RepositoryFactory.CreateAuditRepository(uow); - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } - - #region Event Handlers - - public static event TypedEventHandler> SavingContainer; - public static event TypedEventHandler> SavedContainer; - public static event TypedEventHandler> DeletingContainer; - public static event TypedEventHandler> DeletedContainer; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; +using umbraco.interfaces; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the DataType Service, which is an easy access to operations involving + /// + public class DataTypeService : ScopeRepositoryService, IDataTypeService + { + + public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + } + + #region Containers + + public Attempt> CreateContainer(int parentId, string name, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + + try + { + var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) + { + Name = name, + ParentId = parentId, + CreatorId = userId + }; + + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.AddOrUpdate(container); + uow.Commit(); + + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); + } + catch (Exception ex) + { + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); + } + } + } + + public EntityContainer GetContainer(int containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public EntityContainer GetContainer(Guid containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public IEnumerable GetContainers(string name, int level) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(name, level); + } + } + + public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) + { + var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + var asInt = x.TryConvertTo(); + if (asInt) return asInt.Result; + return int.MinValue; + }) + .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) + .ToArray(); + + return GetContainers(ancestorIds); + } + + public IEnumerable GetContainers(int[] containerIds) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.GetAll(containerIds); + } + } + + public Attempt SaveContainer(EntityContainer container, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); + + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + repo.AddOrUpdate(container); + uow.Commit(); + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + } + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); + } + + public Attempt DeleteContainer(int containerId, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var container = repo.Get(containerId); + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.Delete(container); + uow.Commit(); + + uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); + + return OperationStatus.Success(evtMsgs); + //TODO: Audit trail ? + } + } + + #endregion + + /// + /// Gets a by its Name + /// + /// Name of the + /// + public IDataTypeDefinition GetDataTypeDefinitionByName(string name) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// + public IDataTypeDefinition GetDataTypeDefinitionById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.Key == id); + + var definitions = repository.GetByQuery(query); + return definitions.FirstOrDefault(); + } + } + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// Collection of objects with a matching contorl id + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + public IEnumerable GetDataTypeDefinitionByControlId(Guid id) + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); + return GetDataTypeDefinitionByPropertyEditorAlias(alias); + } + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + public IEnumerable GetAllDataTypeDefinitions(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets all prevalues for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + public IEnumerable GetPreValuesByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + //now convert the collection to a string list + var collection = repository.GetPreValuesCollectionByDataTypeId(id); + //now convert the collection to a string list + return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); + } + } + + /// + /// Returns the PreValueCollection for the specified data type + /// + /// + /// + public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValuesCollectionByDataTypeId(id); + } + } + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + public string GetPreValueAsString(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValueAsString(id); + } + } + + public Attempt> Move(IDataTypeDefinition toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + var moveInfo = new List>(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); + if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + try + { + EntityContainer container = null; + if (parentId > 0) + { + container = containerRepository.Get(parentId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + moveInfo.AddRange(repository.Move(toMove, container)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + moveEventArgs.MoveInfoCollection = moveInfo; + moveEventArgs.CanCancel = false; + uow.Events.Dispatch(Moved, this, moveEventArgs); + } + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + + public bool IsDataTypeIgnoringUserStartNodes(Guid key) + { + var dataType = GetDataTypeDefinitionById(key); + + if (dataType != null) + { + var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); + if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + { + return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); + } + } + + return false; + } + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) + { + throw new ArgumentException("Cannot save datatype with empty name."); + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + + Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + } + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + public void Save(IEnumerable dataTypeDefinitions, int userId = 0) + { + Save(dataTypeDefinitions, userId, true); + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + foreach (var dataTypeDefinition in dataTypeDefinitions) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); + uow.Commit(); + } + } + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + public void SavePreValues(int dataTypeId, IEnumerable values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) + { + sortOrder = 1; + } + + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + uow.Commit(); + } + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(int dataTypeId, IDictionary values) + { + var dtd = GetDataTypeDefinitionById(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found for id " + dataTypeId); + } + SavePreValues(dtd, values); + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + uow.Commit(); + } + } + + /// + /// This will save a data type and it's pre-values in one transaction + /// + /// + /// + /// + public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + // if preValues contain the data type, override the data type definition accordingly + if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) + dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + + //add/update the dtd + repository.AddOrUpdate(dataTypeDefinition); + + //add/update the prevalues + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + } + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Optional Id of the user issueing the deletion + public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + repository.Delete(dataTypeDefinition); + + Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(Deleted, this, deleteEventArgs); + } + } + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IDataType GetDataTypeById(Guid id) + { + return DataTypesResolver.Current.GetById(id); + } + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IEnumerable GetAllDataTypes() + { + return DataTypesResolver.Current.DataTypes; + } + + private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) + { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + } + + #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 9e119cd28a..de8ee5f49c 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -1,167 +1,168 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the DataType Service, which is an easy access to operations involving - /// - public interface IDataTypeService : IService - { - Attempt> CreateContainer(int parentId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); - EntityContainer GetContainer(int containerId); - EntityContainer GetContainer(Guid containerId); - IEnumerable GetContainers(string folderName, int level); - IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); - IEnumerable GetContainers(int[] containerIds); - Attempt DeleteContainer(int containerId, int userId = 0); - - /// - /// Gets a by its Name - /// - /// Name of the - /// - IDataTypeDefinition GetDataTypeDefinitionByName(string name); - - /// - /// Gets a by its Id - /// - /// Id of the - /// - IDataTypeDefinition GetDataTypeDefinitionById(int id); - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - IDataTypeDefinition GetDataTypeDefinitionById(Guid id); - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - IEnumerable GetAllDataTypeDefinitions(params int[] ids); - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - void Save(IEnumerable dataTypeDefinitions, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Id of the user issueing the deletion - void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IDataType GetDataTypeById(Guid id); - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IEnumerable GetAllDataTypes(); - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - IEnumerable GetDataTypeDefinitionByControlId(Guid id); - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); - - /// - /// Gets all values for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - IEnumerable GetPreValuesByDataTypeId(int id); - - /// - /// Gets a pre-value collection by data type id - /// - /// - /// - PreValueCollection GetPreValuesCollectionByDataTypeId(int id); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - void SavePreValues(int dataTypeId, IEnumerable values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(int dataTypeId, IDictionary values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// The DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); - - /// - /// Saves the data type and it's prevalues - /// - /// - /// - /// - void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - string GetPreValueAsString(int id); - - Attempt> Move(IDataTypeDefinition toMove, int parentId); - - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Core.Services +{ + /// + /// Defines the DataType Service, which is an easy access to operations involving + /// + public interface IDataTypeService : IService + { + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); + EntityContainer GetContainer(int containerId); + EntityContainer GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); + IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); + IEnumerable GetContainers(int[] containerIds); + Attempt DeleteContainer(int containerId, int userId = 0); + + /// + /// Gets a by its Name + /// + /// Name of the + /// + IDataTypeDefinition GetDataTypeDefinitionByName(string name); + + /// + /// Gets a by its Id + /// + /// Id of the + /// + IDataTypeDefinition GetDataTypeDefinitionById(int id); + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + IDataTypeDefinition GetDataTypeDefinitionById(Guid id); + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + IEnumerable GetAllDataTypeDefinitions(params int[] ids); + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + void Save(IEnumerable dataTypeDefinitions, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Id of the user issueing the deletion + void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IDataType GetDataTypeById(Guid id); + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IEnumerable GetAllDataTypes(); + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + IEnumerable GetDataTypeDefinitionByControlId(Guid id); + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); + + /// + /// Gets all values for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + IEnumerable GetPreValuesByDataTypeId(int id); + + /// + /// Gets a pre-value collection by data type id + /// + /// + /// + PreValueCollection GetPreValuesCollectionByDataTypeId(int id); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + void SavePreValues(int dataTypeId, IEnumerable values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(int dataTypeId, IDictionary values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// The DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); + + /// + /// Saves the data type and it's prevalues + /// + /// + /// + /// + void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + string GetPreValueAsString(int id); + + Attempt> Move(IDataTypeDefinition toMove, int parentId); + + bool IsDataTypeIgnoringUserStartNodes(Guid dataTypeId); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index b81e62a66b..3b6fb5f9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - ignoreUserStartNodes: "@", + datatypeId: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -62,10 +62,10 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } - //append ignoreUserStartNodes value if there is one - if (scope.ignoreUserStartNodes) { - searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; - } + //append dataTypeId value if there is one + if (scope.datatypeId) { + searchArgs["dataTypeId"] = scope.datatypeId; + } searcher(searchArgs).then(function (data) { scope.searchCallback(data); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 77e587bcc6..31eccfb5aa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -19,8 +19,8 @@ * contentResource.getById(1234) * .then(function(data) { * $scope.content = data; - * }); - * + * }); + * **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { @@ -83,7 +83,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * $scope.complete = true; * }); - * + * * @param {Object} args arguments object * @param {Int} args.parentId the ID of the parent node * @param {Array} options.sortedIds array of node IDs as they should be sorted @@ -124,9 +124,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * alert("node didnt move:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.idd the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to @@ -167,9 +167,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was copied"); * }, function(err){ - * alert("node wasnt copy:" + err.data.Message); + * alert("node wasnt copy:" + err.data.Message); * }); - * + * * @param {Object} args arguments object * @param {Int} args.id the ID of the node to copy * @param {Int} args.parentId the ID of the parent node to copy to @@ -208,9 +208,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was unpulished"); * }, function(err){ - * alert("node wasnt unpublished:" + err.data.Message); + * alert("node wasnt unpublished:" + err.data.Message); * }); - * + * * @param {Int} id the ID of the node to unpublish * @returns {Promise} resourcePromise object. * @@ -242,8 +242,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -270,9 +270,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of content item to delete + * + * + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -308,20 +308,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getById(1234)
           *    .then(function(content) {
-          *        var myDoc = content; 
+          *        var myDoc = content;
           *        alert('its here!');
           *    });
-          * 
- * + * + * * @param {Int} id id of content item to return * @param {Object} options optional options object - * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @param {Guid} options.dataTypeId set to determine whether to allow a user to choose nodes that they normally don't have access to * @returns {Promise} resourcePromise object containing the content item. * */ getById: function (id, options) { var defaults = { - ignoreUserStartNodes: false + dataTypeId: null }; if (options === undefined) { options = {}; @@ -336,7 +336,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + [{ id: id }, { dataTypeId: options.dataTypeId }])), 'Failed to retrieve data for content id ' + id); }, @@ -385,12 +385,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getByIds( [1234,2526,28262])
           *    .then(function(contentArray) {
-          *        var myDoc = contentArray; 
+          *        var myDoc = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * - * @param {Array} ids ids of content items to return as an array + * + * + * @param {Array} ids ids of content items to return as an array * @returns {Promise} resourcePromise object containing the content items array. * */ @@ -418,28 +418,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * + * * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * * The scaffold is used to build editors for content that has not yet been populated with data. - * + * * ##usage *
           * contentResource.getScaffold(1234, 'homepage')
           *    .then(function(scaffold) {
           *        var myDoc = scaffold;
-          *        myDoc.name = "My new document"; 
+          *        myDoc.name = "My new document";
           *
           *        contentResource.publish(myDoc, true)
           *            .then(function(content){
           *                alert("Retrieved, updated and published again");
           *            });
           *    });
-          * 
- * + * + * * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on + * @param {String} alias contenttype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the content scaffold. * */ @@ -479,8 +479,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function(url) { * alert('its here!'); * }); - * - * + * + * * @param {Int} id Id of node to return the public url to * @returns {Promise} resourcePromise object containing the url. * @@ -506,11 +506,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + * + * * @param {Int} parentid id of content item to return children of * @param {Object} options optional options object * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 @@ -594,10 +594,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('You are allowed to publish this item'); * }); - * + * * * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -637,9 +637,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -650,11 +650,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and saved again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -679,9 +679,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -692,11 +692,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and published again");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -715,7 +715,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item, and notifies any subscribers about a pending publication - * + * * ##usage *
           * contentResource.getById(1234)
@@ -726,11 +726,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                alert("Retrieved, updated and notication send off");
           *            });
           *    });
-          * 
- * + * + * * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document * @returns {Promise} resourcePromise object containing the saved content item. * */ @@ -748,15 +748,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Publishes a content item with a given ID - * + * * ##usage *
           * contentResource.publishById(1234)
           *    .then(function(content) {
           *        alert("published");
           *    });
-          * 
- * + * + * * @param {Int} id The ID of the conten to publish * @returns {Promise} resourcePromise object containing the published content item. * diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 4875491dc6..fff4ddde37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -1,584 +1,584 @@ -/** - * @ngdoc service - * @name umbraco.resources.entityResource - * @description Loads in basic data for all entities - * - * ##What is an entity? - * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. - * - * ##What is the difference between entity and content/media/etc...? - * the entity only contains the basic node data, name, id and guid, whereas content - * nodes fetched through the content service also contains additional all of the content property data, etc.. - * This is the same principal for all entity types. Any user that is logged in to the back office will have access - * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. - * - * ##Entity object types? - * You need to specify the type of object you want returned. - * - * The core object types are: - * - * - Document - * - Media - * - Member - * - Template - * - DocumentType - * - MediaType - * - MemberType - * - Macro - * - User - * - Language - * - Domain - * - DataType - **/ -function entityResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getSafeAlias: function (value, camelCase) { - - if (!value) { - return ""; - } - value = value.replace("#", ""); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetSafeAlias", { value: value, camelCase: camelCase })), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPath - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a path, given a node ID and type - * - * ##usage - *
-         * entityResource.getPath(id, type)
-         *    .then(function(pathArray) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getPath: function (id, type) { - - if (id === -1 || id === "-1") { - return "-1"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPath", - [{ id: id }, {type: type }])), - 'Failed to retrieve path for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getUrl - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a url, given a node ID and type - * - * ##usage - *
-         * entityResource.getUrl(id, type)
-         *    .then(function(url) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getUrl: function (id, type) { - - if (id === -1 || id === "-1") { - return ""; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetUrl", - [{ id: id }, {type: type }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getById - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
-         * //get media by id
-         * entityResource.getEntityById(0, "Media")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of entity to return - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getById: function (id, type) { - - if (id === -1 || id === "-1") { - return null; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetById", - [{ id: id }, { type: type }])), - 'Failed to retrieve entity data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByIds - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a collection of ids - * - * ##usage - *
-         * //Get templates for ids
-         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
-         *    .then(function(templateArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of entities to return as an array - * @param {string} type type name - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - getByIds: function (ids, type) { - - var query = "type=" + type; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByIds", - query), - { - ids: ids - }), - 'Failed to retrieve entity data for ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByQuery - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity from a given xpath - * - * ##usage - *
-         * //get content by xpath
-         * entityResource.getByQuery("$current", -1, "Document")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {string} query xpath to use in query - * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
-         *
-         * //Only return media
-         * entityResource.getAll("Media")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {string} type Object type name - * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server - * @param {string} postFilterParams optional parameters for the postFilter expression - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAll: function (type, postFilter, postFilterParams) { - - //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAll", - query)), - 'Failed to retrieve entity data for type ' + type); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets ancestor entities for a given item - * - * - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAncestors: function (id, type, options) { - var defaults = { - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAncestors", - [ - { id: id }, - { type: type }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - 'Failed to retrieve ancestor data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets children entities for a given item - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getChildren: function (id, type) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetChildren", - [{ id: id }, { type: type }])), - 'Failed to retrieve child data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged children of a content item with a given id - * - * ##usage - *
-          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray; 
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 - * @param {Int} options.pageNumber if paging data, current page index, default = 100 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedChildren: function (parentId, type, options) { - - var defaults = { - pageSize: 1, - pageNumber: 100, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedChildren", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedDescendants - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged descendants of a content item with a given id - * - * ##usage - *
-          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray; 
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return descendants of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 - * @param {Int} options.pageNumber if paging data, current page index, default = 1 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getPagedDescendants: function (parentId, type, options) { - - var defaults = { - pageSize: 100, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPagedDescendants", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter), - ignoreUserStartNodes: options.ignoreUserStartNodes - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#search - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a lucene query and a type - * - * ##usage - *
-         * entityResource.search("news", "Media")
-         *    .then(function(mediaArray) {
-         *        var myDoc = mediaArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {String} Query search query - * @param {String} Type type of conten to search - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - search: function (query, type, options, canceler) { - - var defaults = { - searchFrom: null, - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - { - query: query, - type: type, - searchFrom: options.searchFrom, - ignoreUserStartNodes: options.ignoreUserStartNodes - }), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#searchAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities from all available search indexes, given a lucene query - * - * ##usage - *
-         * entityResource.searchAll("bob")
-         *    .then(function(array) {
-         *        var myDoc = array; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {String} Query search query - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - searchAll: function (query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }]), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('entityResource', entityResource); +/** + * @ngdoc service + * @name umbraco.resources.entityResource + * @description Loads in basic data for all entities + * + * ##What is an entity? + * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most + * basic properties used to display the item in trees, lists and navigation. + * + * ##What is the difference between entity and content/media/etc...? + * the entity only contains the basic node data, name, id and guid, whereas content + * nodes fetched through the content service also contains additional all of the content property data, etc.. + * This is the same principal for all entity types. Any user that is logged in to the back office will have access + * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. + * + * ##Entity object types? + * You need to specify the type of object you want returned. + * + * The core object types are: + * + * - Document + * - Media + * - Member + * - Template + * - DocumentType + * - MediaType + * - MemberType + * - Macro + * - User + * - Language + * - Domain + * - DataType + **/ +function entityResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + getSafeAlias: function (value, camelCase) { + + if (!value) { + return ""; + } + value = value.replace("#", ""); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetSafeAlias", { value: value, camelCase: camelCase })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPath + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a path, given a node ID and type + * + * ##usage + *
+         * entityResource.getPath(id, type)
+         *    .then(function(pathArray) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getPath: function (id, type) { + + if (id === -1 || id === "-1") { + return "-1"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPath", + [{ id: id }, {type: type }])), + 'Failed to retrieve path for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getUrl + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a url, given a node ID and type + * + * ##usage + *
+         * entityResource.getUrl(id, type)
+         *    .then(function(url) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function (id, type) { + + if (id === -1 || id === "-1") { + return ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ id: id }, {type: type }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getById + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
+         * //get media by id
+         * entityResource.getEntityById(0, "Media")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id id of entity to return + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getById: function (id, type) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetById", + [{ id: id }, { type: type }])), + 'Failed to retrieve entity data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByIds + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a collection of ids + * + * ##usage + *
+         * //Get templates for ids
+         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
+         *    .then(function(templateArray) {
+         *        var myDoc = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Array} ids ids of entities to return as an array + * @param {string} type type name + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + getByIds: function (ids, type) { + + var query = "type=" + type; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByIds", + query), + { + ids: ids + }), + 'Failed to retrieve entity data for ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
+         * //get content by xpath
+         * entityResource.getByQuery("$current", -1, "Document")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
+         *
+         * //Only return media
+         * entityResource.getAll("Media")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} type Object type name + * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server + * @param {string} postFilterParams optional parameters for the postFilter expression + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAll: function (type, postFilter, postFilterParams) { + + //need to build the query string manually + var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function(val, key) { + query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; + counter++; + }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAll", + query)), + 'Failed to retrieve entity data for type ' + type); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAncestors + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets ancestor entities for a given item + * + * + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAncestors: function (id, type, options) { + var defaults = { + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAncestors", + [ + { id: id }, + { type: type }, + { dataTypeId: options.dataTypeId } + ])), + 'Failed to retrieve ancestor data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets children entities for a given item + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getChildren: function (id, type) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetChildren", + [{ id: id }, { type: type }])), + 'Failed to retrieve child data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
+          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray;
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedChildren: function (parentId, type, options) { + + var defaults = { + pageSize: 1, + pageNumber: 100, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedChildren", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedDescendants + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged descendants of a content item with a given id + * + * ##usage + *
+          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray;
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return descendants of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedDescendants: function (parentId, type, options) { + + var defaults = { + pageSize: 100, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedDescendants", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#search + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a lucene query and a type + * + * ##usage + *
+         * entityResource.search("news", "Media")
+         *    .then(function(mediaArray) {
+         *        var myDoc = mediaArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @param {String} Type type of conten to search + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + search: function (query, type, options, canceler) { + + var defaults = { + searchFrom: null, + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "Search", + { + query: query, + type: type, + searchFrom: options.searchFrom, + dataTypeId: options.dataTypeId + }), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities from all available search indexes, given a lucene query + * + * ##usage + *
+         * entityResource.searchAll("bob")
+         *    .then(function(array) {
+         *        var myDoc = array;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchAll: function (query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }]), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('entityResource', entityResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index e968913047..6600aec0c7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -1,559 +1,559 @@ -/** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ -function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * mediaResource.sort({ sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * mediaResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
-          *      alert("node didnt move:" + err.data.Message);
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } - } - } - - return { - errorMsg: errorMsg - }; - } - }); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *        var myMedia = media;
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
-          * mediaResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
-          * mediaResource.getByIds( [1234,2526,28262])
-          *    .then(function(mediaArray) {
-          *        var myDoc = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
-          * mediaResource.getScaffold(1234, 'folder')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
-          *        myDoc.name = "My new media item";
-          *
-          *        mediaResource.save(myDoc, true)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); - - }, - - rootMedia: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); - - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
-          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true, - ignoreUserStartNodes: false - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; - } - if (angular.isString(v)) { - return v === "true"; - } - if (typeof v === "boolean") { - return v; - } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *          media.name = "I want a new name!";
-          *          mediaResource.save(media, false)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
-          * mediaResource.addFolder("My gallery", 1234)
-          *    .then(function(folder) {
-          *        alert('New folder');
-          *    });
-          * 
- * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##usage - *
-          * mediaResource.getChildFolders(1234)
-          *    .then(function(data) {
-          *        alert('folders');
-          *    });
-          * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function (parentId) { - if (!parentId) { - parentId = -1; - } - - //NOTE: This will return a max of 500 folders, if more is required it needs to be paged - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { - id: parentId - })), - 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
-          * mediaResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
-          * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ - search: function (query, pageNumber, pageSize, searchFrom) { - - var args = [ - { "query": query }, - { "pageNumber": pageNumber }, - { "pageSize": pageSize }, - { "searchFrom": searchFrom } - ]; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('mediaResource', mediaResource); +/** + * @ngdoc service + * @name umbraco.resources.mediaResource + * @description Loads in data for media + **/ +function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } + + return { + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+          * var ids = [123,34533,2334,23434];
+          * mediaResource.sort({ sortedIds: ids })
+          *    .then(function() {
+          *        $scope.complete = true;
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+          * mediaResource.move({ parentId: 1244, id: 123 })
+          *    .then(function() {
+          *        alert("node was moved");
+          *    }, function(err){
+          *      alert("node didnt move:" + err.data.Message);
+          *    });
+          * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; + } + } + } + + return { + errorMsg: errorMsg + }; + } + }); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
+          * mediaResource.getById(1234)
+          *    .then(function(media) {
+          *        var myMedia = media;
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
+          * mediaResource.deleteById(1234)
+          *    .then(function() {
+          *        alert('its gone!');
+          *    });
+          * 
+ * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
+          * mediaResource.getByIds( [1234,2526,28262])
+          *    .then(function(mediaArray) {
+          *        var myDoc = contentArray;
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
+          * mediaResource.getScaffold(1234, 'folder')
+          *    .then(function(scaffold) {
+          *        var myDoc = scaffold;
+          *        myDoc.name = "My new media item";
+          *
+          *        mediaResource.save(myDoc, true)
+          *            .then(function(media){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); + + }, + + rootMedia: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); + + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
+          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray;
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true, + dataTypeId: null + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter }, + { dataTypeId: options.dataTypeId } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+          * mediaResource.getById(1234)
+          *    .then(function(media) {
+          *          media.name = "I want a new name!";
+          *          mediaResource.save(media, false)
+          *            .then(function(media){
+          *                alert("Retrieved, updated and saved again");
+          *            });
+          *    });
+          * 
+ * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
+          * mediaResource.addFolder("My gallery", 1234)
+          *    .then(function(folder) {
+          *        alert('New folder');
+          *    });
+          * 
+ * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##usage + *
+          * mediaResource.getChildFolders(1234)
+          *    .then(function(data) {
+          *        alert('folders');
+          *    });
+          * 
+ * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + //NOTE: This will return a max of 500 folders, if more is required it needs to be paged + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { + id: parentId + })), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
+          * mediaResource.emptyRecycleBin()
+          *    .then(function() {
+          *        alert('its empty!');
+          *    });
+          * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+          * mediaResource.search("my search", 1, 100, -1)
+          *    .then(function(searchResult) {
+          *        alert('it's here!');
+          *    });
+          * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ + search: function (query, pageNumber, pageSize, searchFrom) { + + var args = [ + { "query": query }, + { "pageNumber": pageNumber }, + { "pageSize": pageSize }, + { "searchFrom": searchFrom } + ]; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 0d00678282..24b946cdc8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -1,174 +1,174 @@ -/** - * @ngdoc service - * @name umbraco.services.searchService - * - * - * @description - * Service for handling the main application search, can currently search content, media and members - * - * ##usage - * To use, simply inject the searchService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
- *      searchService.searchMembers({term: 'bob'}).then(function(results){
- *          angular.forEach(results, function(result){
- *                  //returns:
- *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
- *           })          
- *           var result = 
- *       }) 
- * 
- */ -angular.module('umbraco.services') - .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMembers - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default member search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching members - */ - searchMembers: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMemberResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchContent - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default internal content search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching content items - */ - searchContent: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureContentResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMedia - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default media search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching media items - */ - searchMedia: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Media", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMediaResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchAll - * @methodOf umbraco.services.searchService - * - * @description - * Searches all available indexes and returns all results in one collection - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching items - */ - searchAll: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function (resultByType) { +/** + * @ngdoc service + * @name umbraco.services.searchService + * + * + * @description + * Service for handling the main application search, can currently search content, media and members + * + * ##usage + * To use, simply inject the searchService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
+ *      searchService.searchMembers({term: 'bob'}).then(function(results){
+ *          angular.forEach(results, function(result){
+ *                  //returns:
+ *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
+ *           })
+ *           var result =
+ *       })
+ * 
+ */ +angular.module('umbraco.services') + .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { + + return { + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMembers + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default member search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching members + */ + searchMembers: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMemberResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchContent + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default internal content search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching content items + */ + searchContent: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureContentResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMedia + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default media search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching media items + */ + searchMedia: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Media", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMediaResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchAll + * @methodOf umbraco.services.searchService + * + * @description + * Searches all available indexes and returns all results in one collection + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching items + */ + searchAll: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.searchAll(args.term, args.canceler).then(function (data) { + + _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... - // this is done with registered angular services as part of the SearchableTreeAttribute, if that - // is not found, than we format with the default formatter + // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... - if (resultByType.jsSvc) { - var searchFormatterService = $injector.get(resultByType.jsSvc); - if (searchFormatterService) { - if (!resultByType.jsMethod) { - resultByType.jsMethod = "format"; - } - formatterMethod = searchFormatterService[resultByType.jsMethod]; - - if (!formatterMethod) { - throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; - } - } - } - //now apply the formatter for each result - _.each(resultByType.results, function (item) { - formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); + if (resultByType.jsSvc) { + var searchFormatterService = $injector.get(resultByType.jsSvc); + if (searchFormatterService) { + if (!resultByType.jsMethod) { + resultByType.jsMethod = "format"; + } + formatterMethod = searchFormatterService[resultByType.jsMethod]; + + if (!formatterMethod) { + throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; + } + } + } + //now apply the formatter for each result + _.each(resultByType.results, function (item) { + formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - - }); - - return data; - }); - - }, - - //TODO: This doesn't do anything! - setCurrent: function (sectionAlias) { - - var currentSection = sectionAlias; - } - }; - }); + + }); + + return data; + }); + + }, + + //TODO: This doesn't do anything! + setCurrent: function (sectionAlias) { + + var currentSection = sectionAlias; + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 5d6a3bf12c..67d62de223 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -1,170 +1,172 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.dialogOptions; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.dialogTreeEventHandler = $({}); - $scope.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - - //if we have a node ID, we fetch the current node to build the form data - if ($scope.target.id || $scope.target.udi) { - - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; - - if (!$scope.target.path) { - entityResource.getPath(id, "Document").then(function (path) { - $scope.target.path = path; - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: $scope.target.path, - tree: "content" - }); - }); - } - - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } else if ($scope.target.url.length) { +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.dialogOptions; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + debugger; + $scope.dialogTreeEventHandler = $({}); + $scope.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + dataTypeId: $scope.model.dataTypeId, + results: [], + selectedSearchResults: [] + } + + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + + //if we have a node ID, we fetch the current node to build the form data + if ($scope.target.id || $scope.target.udi) { + + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; + + if (!$scope.target.path) { + entityResource.getPath(id, "Document").then(function (path) { + $scope.target.path = path; + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: $scope.target.path, + tree: "content" + }); + }); + } + + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } else if ($scope.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.target.url.search(/(#|\?)/); - if (indexOfAnchor > -1) { - // populate the anchor - $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); - // then rewrite the model and populate the link + if (indexOfAnchor > -1) { + // populate the anchor + $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); + // then rewrite the model and populate the link $scope.target.url = $scope.target.url.substring(0, indexOfAnchor); - } - } - } - - if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } else { - eventsService.emit("dialogs.linkPicker.select", args); - - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } - - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.target.id = args.node.id; - $scope.target.udi = args.node.udi; - $scope.target.name = args.node.name; - - if (args.node.id < 0) { - $scope.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } - - if (!angular.isUndefined($scope.target.isMedia)) { - delete $scope.target.isMedia; - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - //check if any of the items are list views, if so we need to add a custom - // child: A node to activate the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon umb-tree-icon sprTree icon-search", - cssClasses: ["not-published"] - } - ]; - } - }); - } - } - - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - dialogService.mediaPicker({ - startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], - callback: function (media) { - $scope.target.id = media.id; - $scope.target.isMedia = true; - $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); - } - }); - }); - }; - - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; - - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); + } + } + } + + if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } else { + eventsService.emit("dialogs.linkPicker.select", args); + + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.target.id = args.node.id; + $scope.target.udi = args.node.udi; + $scope.target.name = args.node.name; + + if (args.node.id < 0) { + $scope.target.url = "/"; + } else { + contentResource.getById(args.node.id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } + + if (!angular.isUndefined($scope.target.isMedia)) { + delete $scope.target.isMedia; + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + dialogService.mediaPicker({ + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + callback: function (media) { + $scope.target.id = media.id; + $scope.target.isMedia = true; + $scope.target.name = media.name; + $scope.target.url = mediaHelper.resolveFile(media); + } + }); + }); + }; + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html index 3b02853127..c4ac0c2e93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html @@ -1,86 +1,87 @@ -
- - - -
+
+ + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 844ee6e240..539cb418c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -1,431 +1,430 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { - - var tree = null; - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - entityType = "Member"; - } - else if (dialogOptions.section === "media") { - entityType = "Media"; - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filter = dialogOptions.filter.substring(1); - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //check if any of the items are list views, if so we need to add some custom - // children: A node to activate the search, any nodes that have already been - // selected in the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - parent: function () { - return child; - }, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon-search", - cssClasses: ["not-published"] - } - ]; - //add base transition classes to this node - child.cssClasses.push("tree-node-slide-up"); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function(item) { - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return child; - } - }); - }); - } - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); - } - else if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function(child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - if ($scope.multiPicker) { - $scope.select(id); - } - else { - var node = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - $scope.submit(node); - } - } - else { - - if ($scope.multiPicker) { - $scope.select(Number(id)); - } - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.submit(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - $scope.submit(ent); - }); - } - } - } - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function(n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function(c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function(c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function(c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + + var tree = null; + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } + + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; + $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + entityType = "Member"; + } + else if (dialogOptions.section === "media") { + entityType = "Media"; + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //check if any of the items are list views, if so we need to add some custom + // children: A node to activate the search, any nodes that have already been + // selected in the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + parent: function () { + return child; + }, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon-search", + cssClasses: ["not-published"] + } + ]; + //add base transition classes to this node + child.cssClasses.push("tree-node-slide-up"); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function(item) { + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return child; + } + }); + }); + } + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + tree = args.tree; + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + + //add transition classes + var listViewNode = args.node.parent(); + listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); + } + else if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function(child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + if ($scope.multiPicker) { + $scope.select(id); + } + else { + var node = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + $scope.submit(node); + } + } + else { + + if ($scope.multiPicker) { + $scope.select(Number(id)); + } + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.submit(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + $scope.submit(ent); + }); + } + } + } + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function(n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function(c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function(c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function(c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function(results) {; + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 4391e50c28..14bcf58f5a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,7 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" - ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" + datatype-id="{{searchInfo.dataTypeId}}" section="content"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 79b9362d3f..8a1c3eb9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -18,11 +18,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", searchFromId: null, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; $scope.showTarget = $scope.model.hideTarget !== true; if (dialogOptions.currentTarget) { @@ -46,7 +46,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", }); // if a link exists, get the properties to build the anchor name list - contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -88,7 +88,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(args.node.id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -120,7 +120,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, show: true, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, submit: function (model) { var media = model.selectedImages[0]; @@ -133,7 +133,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; - // make sure the content tree has nothing highlighted + // make sure the content tree has nothing highlighted $scope.dialogTreeEventHandler.syncTree({ path: "-1", tree: "content" diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index c9f3ad54a7..4cc7b0fd46 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -43,14 +43,13 @@
Link to page
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index e3d9326c53..e7d14c9871 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,426 +1,426 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - - var dialogOptions = $scope.model; - - $scope.disableFolderSelect = dialogOptions.disableFolderSelect; - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); - $scope.lockedFolder = true; - - var userStartNodes = []; - - var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); - if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); - } else { - // Use whitelist of allowed file types if provided - if (allowedUploadFiles !== '') { - $scope.acceptedFileTypes = allowedUploadFiles; - } else { - // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); - } - } - - $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; - - $scope.model.selectedImages = []; - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - function onInit() { - userService.getCurrentUser().then(function (userData) { - userStartNodes = userData.startMediaIds; - - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } - }); - } - - function run() { - //default root item - if (!$scope.target) { - if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - entityResource.getById($scope.lastOpenedNode, "media") - .then(ensureWithinStartNode, gotoStartNode); - } else { - gotoStartNode(); - } - } else { - //if a target is specified, go look it up - generally this target will just contain ids not the actual full - //media object so we need to look it up - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id - var altText = $scope.target.altText; - if (id) { - mediaResource.getById(id) - .then(function (node) { - $scope.target = node; - if (ensureWithinStartNode(node)) { - selectImage(node); - $scope.target.url = mediaHelper.resolveFile(node); - $scope.target.altText = altText; - $scope.openDetailsDialog(); - } - }, - gotoStartNode); - } else { - gotoStartNode(); - } - } - } - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function() { - if ($scope.newFolderName) { - $scope.creatingFolder = true; - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - $scope.creatingFolder = false; - $scope.gotoFolder(data); - $scope.showFolderInput = false; - $scope.newFolderName = ""; - }); - } else { - $scope.showFolderInput = false; - } - }; - - $scope.enterSubmitFolder = function(event) { - if (event.keyCode === 13) { - $scope.submitFolder(); - event.stopPropagation(); - } - }; - - $scope.gotoFolder = function(folder) { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(anc) { - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - } else { - $scope.path = []; - } - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function (types) { - $scope.acceptedMediatypes = types; - }); - - $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; - - $scope.currentFolder = folder; - localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - return getChildren(folder.id); - }; - - $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - if ($scope.disableFolderSelect) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - selectImage(image); - } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - if ($scope.showDetails) { - - $scope.target = image; - - // handle both entity and full media object - if (image.image) { - $scope.target.url = image.image; - } else { - $scope.target.url = mediaHelper.resolveFile(image); - } - - $scope.openDetailsDialog(); - } else { - selectImage(image); - } - } - }; - - $scope.clickItemName = function(item) { - if (item.isFolder) { - $scope.gotoFolder(item); - } - }; - - function selectImage(image) { - if (image.selected) { - for (var i = 0; $scope.model.selectedImages.length > i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; - if (image.key === imageInSelection.key) { - image.selected = false; - $scope.model.selectedImages.splice(i, 1); - } - } - } else { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - image.selected = true; - $scope.model.selectedImages.push(image); - } - } - - function deselectAllImages(images) { - for (var i = 0; i < images.length; i++) { - var image = images[i]; - image.selected = false; - } - images.length = 0; - } - - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { - if (files.length === 1 && $scope.model.selectedImages.length === 0) { - var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectImage(image); - } - }); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - function ensureWithinStartNode(node) { - // make sure that last opened node is on the same path as start node - var nodePath = node.path.split(","); - - // also make sure the node is not trashed - if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); - return true; - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - return false; - } - } - - function hasFolderAccess(node) { - var nodePath = node.path ? node.path.split(',') : [node.id]; - - for (var i = 0; i < nodePath.length; i++) { - if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) - return true; - } - - return false; - } - - function gotoStartNode(err) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - - $scope.openDetailsDialog = function() { - - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; - - $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - }; - - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); - - $scope.changeSearch = function() { - $scope.loading = true; - debounceSearchMedia(); - }; - - $scope.toggle = function() { - // Make sure to activate the changeSearch function everytime the toggle is clicked - $scope.changeSearch(); - } - - $scope.changePagination = function(pageNumber) { - $scope.loading = true; - $scope.searchOptions.pageNumber = pageNumber; - searchMedia(); - }; - - function searchMedia() { - $scope.loading = true; - entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { - // update image data to work with image grid - angular.forEach(data.items, - function(mediaItem) { - // set thumbnail and src - mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); - mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); - // set properties to match a media object - mediaItem.properties = []; - if (mediaItem.metaData) { - if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { - mediaItem.properties.push({ - alias: "umbracoWidth", - value: mediaItem.metaData.umbracoWidth.Value - }); - mediaItem.properties.push({ - alias: "umbracoHeight", - value: mediaItem.metaData.umbracoHeight.Value - }); - } - if (mediaItem.metaData.umbracoFile) { - mediaItem.properties.push({ - alias: "umbracoFile", - editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, - value: mediaItem.metaData.umbracoFile.Value - }); - } - } - }); - // update images - $scope.images = data.items ? data.items : []; - // update pagination - if (data.pageNumber > 0) - $scope.searchOptions.pageNumber = data.pageNumber; - if (data.pageSize > 0) - $scope.searchOptions.pageSize = data.pageSize; - $scope.searchOptions.totalItems = data.totalItems; - $scope.searchOptions.totalPages = data.totalPages; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function getChildren(id) { - $scope.loading = true; - return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(data) { - $scope.searchOptions.filter = ""; - $scope.images = data.items ? data.items : []; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function preSelectImages() { - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; - - if ($scope.model && angular.isArray($scope.model.selectedImages)) { - for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; - selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if (folderImage.key === selectedImage.key) { - imageIsSelected = true; - } - } - } - - if (imageIsSelected) { - folderImage.selected = true; - } - } - } - - onInit(); - - }); +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Overlays.MediaPickerController", + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { + + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + + var dialogOptions = $scope.model; + + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); + $scope.lockedFolder = true; + + var userStartNodes = []; + + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + } else { + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + } + + $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; + + $scope.model.selectedImages = []; + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + function onInit() { + userService.getCurrentUser().then(function (userData) { + userStartNodes = userData.startMediaIds; + + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + }); + } + + function run() { + //default root item + if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, "media") + .then(ensureWithinStartNode, gotoStartNode); + } else { + gotoStartNode(); + } + } else { + //if a target is specified, go look it up - generally this target will just contain ids not the actual full + //media object so we need to look it up + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id + var altText = $scope.target.altText; + if (id) { + mediaResource.getById(id) + .then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectImage(node); + $scope.target.url = mediaHelper.resolveFile(node); + $scope.target.altText = altText; + $scope.openDetailsDialog(); + } + }, + gotoStartNode); + } else { + gotoStartNode(); + } + } + } + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function() { + if ($scope.newFolderName) { + $scope.creatingFolder = true; + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + $scope.creatingFolder = false; + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ""; + }); + } else { + $scope.showFolderInput = false; + } + }; + + $scope.enterSubmitFolder = function(event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + + $scope.gotoFolder = function(folder) { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) + .then(function(anc) { + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + } else { + $scope.path = []; + } + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function (types) { + $scope.acceptedMediatypes = types; + }); + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; + + $scope.currentFolder = folder; + localStorageService.set("umbLastOpenedMediaNodeId", folder.id); + return getChildren(folder.id); + }; + + $scope.clickHandler = function(image, event, index) { + if (image.isFolder) { + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + selectImage(image); + } + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + if ($scope.showDetails) { + + $scope.target = image; + + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + } + }; + + $scope.clickItemName = function(item) { + if (item.isFolder) { + $scope.gotoFolder(item); + } + }; + + function selectImage(image) { + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { + var imageInSelection = $scope.model.selectedImages[i]; + if (image.key === imageInSelection.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + } + } + } else { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); + } + } + + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + + $scope.onUploadComplete = function(files) { + $scope.gotoFolder($scope.currentFolder).then(function() { + if (files.length === 1 && $scope.model.selectedImages.length === 0) { + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); + } + }); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + function ensureWithinStartNode(node) { + // make sure that last opened node is on the same path as start node + var nodePath = node.path.split(","); + + // also make sure the node is not trashed + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { + $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); + return true; + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + return false; + } + } + + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + + function gotoStartNode(err) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + + $scope.openDetailsDialog = function() { + + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + + $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + }; + + var debounceSearchMedia = _.debounce(function() { + $scope.$apply(function() { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); + + $scope.changeSearch = function() { + $scope.loading = true; + debounceSearchMedia(); + }; + + $scope.toggle = function() { + // Make sure to activate the changeSearch function everytime the toggle is clicked + $scope.changeSearch(); + } + + $scope.changePagination = function(pageNumber) { + $scope.loading = true; + $scope.searchOptions.pageNumber = pageNumber; + searchMedia(); + }; + + function searchMedia() { + $scope.loading = true; + entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) + .then(function(data) { + // update image data to work with image grid + angular.forEach(data.items, + function(mediaItem) { + // set thumbnail and src + mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); + mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); + // set properties to match a media object + mediaItem.properties = []; + if (mediaItem.metaData) { + if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { + mediaItem.properties.push({ + alias: "umbracoWidth", + value: mediaItem.metaData.umbracoWidth.Value + }); + mediaItem.properties.push({ + alias: "umbracoHeight", + value: mediaItem.metaData.umbracoHeight.Value + }); + } + if (mediaItem.metaData.umbracoFile) { + mediaItem.properties.push({ + alias: "umbracoFile", + editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, + value: mediaItem.metaData.umbracoFile.Value + }); + } + } + }); + // update images + $scope.images = data.items ? data.items : []; + // update pagination + if (data.pageNumber > 0) + $scope.searchOptions.pageNumber = data.pageNumber; + if (data.pageSize > 0) + $scope.searchOptions.pageSize = data.pageSize; + $scope.searchOptions.totalItems = data.totalItems; + $scope.searchOptions.totalPages = data.totalPages; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function getChildren(id) { + $scope.loading = true; + return mediaResource.getChildren(id, { dataTypeId: $scope.model.dataTypeId }) + .then(function(data) { + $scope.searchOptions.filter = ""; + $scope.images = data.items ? data.items : []; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function preSelectImages() { + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + + if ($scope.model && angular.isArray($scope.model.selectedImages)) { + for (var selectedImageIndex = 0; + selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + } + + if (imageIsSelected) { + folderImage.selected = true; + } + } + } + + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index e1ce332b48..b4dd34dfff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -16,7 +16,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchFromId: dialogOptions.startNodeId, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] } @@ -138,8 +138,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", if (dialogOptions.startNodeId) params.push("startNodeId=" + dialogOptions.startNodeId); - if (dialogOptions.ignoreUserStartNodes) - params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + if (dialogOptions.dataTypeId) + params.push("dataTypeId=" + dialogOptions.dataTypeId); if (dialogOptions.customTreeParams) params.push(dialogOptions.customTreeParams); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index c338e3402c..cfcbcfac75 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,56 +1,56 @@ -
- -
- -
- - -
- - - - - - {{ emptyStateMessage }} - - -
- - -
- -
- - - - -
+
+ +
+ +
+ + +
+ + + + + + {{ emptyStateMessage }} + + +
+ + +
+ +
+ + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ce836a8d68..627baa3e7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,366 +1,366 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it - -function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { - - var unsubscribe; - - function subscribe() { - unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - } - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function startWatch() { - //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required - // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable - // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. - // In their source code there is no event so we need to just subscribe to our model changes here. - //This also makes it easier to manage models, we update one and the rest will just work. - $scope.$watch(function () { - //return the joined Ids as a string to watch - return _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }).join(); - }, function (newVal) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - - //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { - $scope.contentPickerForm.minCount.$setValidity("minCount", false); - } - else { - $scope.contentPickerForm.minCount.$setValidity("minCount", true); - } - - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); - } - - setSortingState($scope.renderModel); - - }); - } - - $scope.renderModel = []; - - $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; - - //the default pre-values - var defaultConfig = { - multiPicker: false, - showOpenButton: false, - showEditButton: false, - showPathOnHover: false, - ignoreUserStartNodes: false, - maxNumber: 1, - minNumber : 0, - startNode: { - query: "", - type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker - } - }; - - // sortable options - $scope.sortableOptions = { - axis: "y", - containment: "parent", - distance: 10, - opacity: 0.7, - tolerance: "pointer", - scroll: true, - zIndex: 6000, - update: function (e, ui) { - angularHelper.getCurrentForm($scope).$setDirty(); - } - }; - - if ($scope.model.config) { - //merge the server config on top of the default config, then set the server config to use the result - $scope.model.config = angular.extend(defaultConfig, $scope.model.config); - } - - //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! - $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); - $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); - $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); - $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); - - var entityType = $scope.model.config.startNode.type === "member" - ? "Member" - : $scope.model.config.startNode.type === "media" - ? "Media" - : "Document"; - $scope.allowOpenButton = entityType === "Document"; - $scope.allowEditButton = entityType === "Document"; - $scope.allowRemoveButton = true; - - //the dialog options for the picker - var dialogOptions = { - multiPicker: $scope.model.config.multiPicker, - entityType: entityType, - filterCssClass: "not-allowed not-published", - startNodeId: null, - currentNode: editorState ? editorState.current : null, - callback: function (data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - angularHelper.getCurrentForm($scope).$setDirty(); - }, - treeAlias: $scope.model.config.startNode.type, - section: $scope.model.config.startNode.type, - idType: "int" - }; - - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - angular.extend(dialogOptions, $scope.model.config); - - //We need to manually handle the filter for members here since the tree displayed is different and only contains - // searchable list views - if (entityType === "Member") { - //first change the not allowed filter css class - dialogOptions.filterCssClass = "not-allowed"; - var currFilter = dialogOptions.filter; - //now change the filter to be a method - dialogOptions.filter = function(i) { - //filter out the list view nodes - if (i.metaData.isContainer) { - return true; - } - if (!currFilter) { - return false; - } - //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, - // but not much we can do about that since members require special filtering. - var filterItem = currFilter.toLowerCase().split(','); - var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; - if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { - return true; - } - - return false; - } - } - - if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { - //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query - dialogOptions.startNodeId = -1; - } else if ($scope.model.config.startNode.query) { - //if we have a query for the startnode, we will use that. - var rootId = $routeParams.id; - entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { - dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; - }); - } - else { - dialogOptions.startNodeId = $scope.model.config.startNode.id; - } - - //dialog - $scope.openContentPicker = function() { - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treepicker"; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - if (angular.isArray(model.selection)) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - angularHelper.getCurrentForm($scope).$setDirty(); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - }; - - $scope.remove = function (index) { - $scope.renderModel.splice(index, 1); - angularHelper.getCurrentForm($scope).$setDirty(); - }; - - $scope.showNode = function (index) { - var item = $scope.renderModel[index]; - var id = item.id; - var section = $scope.model.config.startNode.type.toLowerCase(); - - entityResource.getPath(id, entityType).then(function (path) { - navigationService.changeSection(section); - navigationService.showTree(section, { - tree: section, path: path, forceReload: false, activate: true - }); - var routePath = section + "/" + section + "/edit/" + id.toString(); - $location.path(routePath).search(""); - }); - } - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - - var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; - - if (currIds.indexOf(itemId) < 0) { - setEntityUrl(item); - } - }; - - $scope.clear = function () { - $scope.renderModel = []; - }; - - $scope.openMiniEditor = function(node) { - miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ - // update the node - node.name = updatedNode.name; - node.published = updatedNode.hasPublishedVersion; - if(entityType !== "Member") { - entityResource.getUrl(updatedNode.id, entityType).then(function(data){ - node.url = data; - }); - } - }); - }; - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - if(unsubscribe) { - unsubscribe(); - } - }); - - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - - //load current data if anything selected - if (modelIds.length > 0) { - entityResource.getByIds(modelIds, entityType).then(function(data) { - - _.each(modelIds, - function(id, i) { - var entity = _.find(data, - function(d) { - return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); - }); - - if (entity) { - setEntityUrl(entity); - } - - }); - - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - }); - } - else { - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - } - - function setEntityUrl(entity) { - - // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function(data){ - // update url - angular.forEach($scope.renderModel, function(item){ - if (item.id === entity.id) { - if (entity.trashed) { - item.url = localizationService.dictionary.general_recycleBin; - } else { - item.url = data; - } - } - }); - }); - } - - // add the selected item to the renderModel - // if it needs to show a url the item will get - // updated when the url comes back from server - addSelectedItem(entity); - - } - - function addSelectedItem(item) { - - // set icon - if(item.icon) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - } - - // set default icon - if (!item.icon) { - switch (entityType) { - case "Document": - item.icon = "icon-document"; - break; - case "Media": - item.icon = "icon-picture"; - break; - case "Member": - item.icon = "icon-user"; - break; - } - } - - $scope.renderModel.push({ - "name": item.name, - "id": item.id, - "udi": item.udi, - "icon": item.icon, - "path": item.path, - "url": item.url, - "trashed": item.trashed, - "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true - // only content supports published/unpublished content so we set everything else to published so the UI looks correct - }); - - } - - function setSortingState(items) { - // disable sorting if the list only consist of one item - if(items.length > 1) { - $scope.sortableOptions.disabled = false; - } else { - $scope.sortableOptions.disabled = true; - } - } - -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it + +function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { + + var unsubscribe; + + function subscribe() { + unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + }); + } + + function trim(str, chr) { + var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + + function startWatch() { + //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required + // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable + // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. + // In their source code there is no event so we need to just subscribe to our model changes here. + //This also makes it easier to manage models, we update one and the rest will just work. + $scope.$watch(function () { + //return the joined Ids as a string to watch + return _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }).join(); + }, function (newVal) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + + //Validate! + if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + $scope.contentPickerForm.minCount.$setValidity("minCount", false); + } + else { + $scope.contentPickerForm.minCount.$setValidity("minCount", true); + } + + if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); + } + + setSortingState($scope.renderModel); + + }); + } + + $scope.renderModel = []; + + $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; + + //the default pre-values + var defaultConfig = { + multiPicker: false, + showOpenButton: false, + showEditButton: false, + showPathOnHover: false, + dataTypeId: null, + maxNumber: 1, + minNumber : 0, + startNode: { + query: "", + type: "content", + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + } + }; + + // sortable options + $scope.sortableOptions = { + axis: "y", + containment: "parent", + distance: 10, + opacity: 0.7, + tolerance: "pointer", + scroll: true, + zIndex: 6000, + update: function (e, ui) { + angularHelper.getCurrentForm($scope).$setDirty(); + } + }; + + if ($scope.model.config) { + //merge the server config on top of the default config, then set the server config to use the result + $scope.model.config = angular.extend(defaultConfig, $scope.model.config); + } + + //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! + $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); + $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); + $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); + $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); + + var entityType = $scope.model.config.startNode.type === "member" + ? "Member" + : $scope.model.config.startNode.type === "media" + ? "Media" + : "Document"; + $scope.allowOpenButton = entityType === "Document"; + $scope.allowEditButton = entityType === "Document"; + $scope.allowRemoveButton = true; + + //the dialog options for the picker + var dialogOptions = { + multiPicker: $scope.model.config.multiPicker, + entityType: entityType, + filterCssClass: "not-allowed not-published", + startNodeId: null, + currentNode: editorState ? editorState.current : null, + callback: function (data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + angularHelper.getCurrentForm($scope).$setDirty(); + }, + treeAlias: $scope.model.config.startNode.type, + section: $scope.model.config.startNode.type, + idType: "int" + }; + + dialogOptions.dataTypeId = $scope.model.dataTypeId; + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + // pre-value config on to the dialog options + angular.extend(dialogOptions, $scope.model.config); + + //We need to manually handle the filter for members here since the tree displayed is different and only contains + // searchable list views + if (entityType === "Member") { + //first change the not allowed filter css class + dialogOptions.filterCssClass = "not-allowed"; + var currFilter = dialogOptions.filter; + //now change the filter to be a method + dialogOptions.filter = function(i) { + //filter out the list view nodes + if (i.metaData.isContainer) { + return true; + } + if (!currFilter) { + return false; + } + //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, + // but not much we can do about that since members require special filtering. + var filterItem = currFilter.toLowerCase().split(','); + var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; + if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { + return true; + } + + return false; + } + } + + if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { + //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query + dialogOptions.startNodeId = -1; + } else if ($scope.model.config.startNode.query) { + //if we have a query for the startnode, we will use that. + var rootId = $routeParams.id; + entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { + dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; + }); + } + else { + dialogOptions.startNodeId = $scope.model.config.startNode.id; + } + + //dialog + $scope.openContentPicker = function() { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treepicker"; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + }; + + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + angularHelper.getCurrentForm($scope).$setDirty(); + }; + + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, path: path, forceReload: false, activate: true + }); + var routePath = section + "/" + section + "/edit/" + id.toString(); + $location.path(routePath).search(""); + }); + } + + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + + var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; + + if (currIds.indexOf(itemId) < 0) { + setEntityUrl(item); + } + }; + + $scope.clear = function () { + $scope.renderModel = []; + }; + + $scope.openMiniEditor = function(node) { + miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ + // update the node + node.name = updatedNode.name; + node.published = updatedNode.hasPublishedVersion; + if(entityType !== "Member") { + entityResource.getUrl(updatedNode.id, entityType).then(function(data){ + node.url = data; + }); + } + }); + }; + + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + if(unsubscribe) { + unsubscribe(); + } + }); + + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + + //load current data if anything selected + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, entityType).then(function(data) { + + _.each(modelIds, + function(id, i) { + var entity = _.find(data, + function(d) { + return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); + }); + + if (entity) { + setEntityUrl(entity); + } + + }); + + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + }); + } + else { + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + } + + function setEntityUrl(entity) { + + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function(data){ + // update url + angular.forEach($scope.renderModel, function(item){ + if (item.id === entity.id) { + if (entity.trashed) { + item.url = localizationService.dictionary.general_recycleBin; + } else { + item.url = data; + } + } + }); + }); + } + + // add the selected item to the renderModel + // if it needs to show a url the item will get + // updated when the url comes back from server + addSelectedItem(entity); + + } + + function addSelectedItem(item) { + + // set icon + if(item.icon) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + } + + // set default icon + if (!item.icon) { + switch (entityType) { + case "Document": + item.icon = "icon-document"; + break; + case "Media": + item.icon = "icon-picture"; + break; + case "Member": + item.icon = "icon-user"; + break; + } + } + + $scope.renderModel.push({ + "name": item.name, + "id": item.id, + "udi": item.udi, + "icon": item.icon, + "path": item.path, + "url": item.url, + "trashed": item.trashed, + "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true + // only content supports published/unpublished content so we set everything else to published so the UI looks correct + }); + + } + + function setSortingState(items) { + // disable sorting if the list only consist of one item + if(items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } + +} + +angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index a093ccb034..c61f5419ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,11 +1,9 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { - - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { + if ($scope.model.config.ignoreUserStartNodes === "1" ) { $scope.model.config.startNodeId = -1; $scope.model.config.startNodeIsVirtual = true; @@ -22,7 +20,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; + $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index cf81300b32..e3e5d5e072 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -11,12 +11,13 @@ vm.openEmbed = openEmbed; function openLinkPicker(editor, currentTarget, anchorElement) { - + vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -27,12 +28,11 @@ } function openMediaPicker(editor, currentTarget, userData) { - var ignoreUserStartNodes = false; var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; var startNodeIsVirtual = userData.startMediaIds.length !== 1; + if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; startNodeId = -1; startNodeIsVirtual = true; } @@ -40,10 +40,10 @@ vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, + showDetails: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, + dataTypeId: $scope.model.dataTypeId, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 1f1305b0f0..559d0e4a19 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -1,184 +1,185 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { - - //check the pre-values for multi-picker - var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; - var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; - var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - - if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { - $scope.model.config.startNodeId = -1; - $scope.model.config.startNodeIsVirtual = true; - - } else { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); - } - } - - function setupViewModel() { - $scope.mediaItems = []; - $scope.ids = []; - - $scope.isMultiPicker = multiPicker; - - if ($scope.model.value) { - var ids = $scope.model.value.split(','); - - //NOTE: We need to use the entityResource NOT the mediaResource here because - // the mediaResource has server side auth configured for which the user must have - // access to the media section, if they don't they'll get auth errors. The entityResource - // acts differently in that it allows access if the user has access to any of the apps that - // might require it's use. Therefore we need to use the metaData property to get at the thumbnail - // value. - - entityResource.getByIds(ids, "Media").then(function(medias) { - - // The service only returns item results for ids that exist (deleted items are silently ignored). - // This results in the picked items value to be set to contain only ids of picked items that could actually be found. - // Since a referenced item could potentially be restored later on, instead of changing the selected values here based - // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently - // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending - // on whether it is simply resaved or not. - // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders - // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. - - medias = _.map(ids, - function(id) { - var found = _.find(medias, - function(m) { - // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and - // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() - // compares and be completely sure it works. - return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); - }); - if (found) { - return found; - } else { - return { - name: localizationService.dictionary.mediaPicker_deletedItem, - id: $scope.model.config.idType !== "udi" ? id : null, - udi: $scope.model.config.idType === "udi" ? id : null, - icon: "icon-picture", - thumbnail: null, - trashed: true - }; - } - }); - - _.each(medias, - function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - }); - } - } - - setupViewModel(); - - $scope.remove = function(index) { - $scope.mediaItems.splice(index, 1); - $scope.ids.splice(index, 1); - $scope.sync(); - }; - - $scope.goToItem = function(item) { - $location.path('media/media/edit/' + item.id); - }; - - $scope.add = function() { - - $scope.mediaPickerOverlay = { - view: "mediapicker", - title: "Select media", - startNodeId: $scope.model.config.startNodeId, - startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - multiPicker: multiPicker, - onlyImages: onlyImages, - disableFolderSelect: disableFolderSelect, - show: true, - submit: function(model) { - - _.each(model.selectedImages, function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } - else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - }; - - $scope.sortableOptions = { - disabled: !$scope.isMultiPicker, - items: "li:not(.add-wrapper)", - cancel: ".unsortable", - update: function(e, ui) { - var r = []; - // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the - // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the - // watch do all the rest. - $timeout(function(){ - angular.forEach($scope.mediaItems, function(value, key) { - r.push($scope.model.config.idType === "udi" ? value.udi : value.id); - }); - $scope.ids = r; - $scope.sync(); - }, 500, false); - } - }; - - $scope.sync = function() { - $scope.model.value = $scope.ids.join(); - }; - - $scope.showAdd = function () { - if (!multiPicker) { - if ($scope.model.value && $scope.model.value !== "") { - return false; - } - } - return true; - }; - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - }); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it +angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", + function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { + + //check the pre-values for multi-picker + var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; + var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; + var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + + if (!$scope.model.config.startNodeId) { + + + if ( $scope.model.config.ignoreUserStartNodes === "1") { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } + } + + function setupViewModel() { + $scope.mediaItems = []; + $scope.ids = []; + + $scope.isMultiPicker = multiPicker; + + if ($scope.model.value) { + var ids = $scope.model.value.split(','); + + //NOTE: We need to use the entityResource NOT the mediaResource here because + // the mediaResource has server side auth configured for which the user must have + // access to the media section, if they don't they'll get auth errors. The entityResource + // acts differently in that it allows access if the user has access to any of the apps that + // might require it's use. Therefore we need to use the metaData property to get at the thumbnail + // value. + + entityResource.getByIds(ids, "Media").then(function(medias) { + + // The service only returns item results for ids that exist (deleted items are silently ignored). + // This results in the picked items value to be set to contain only ids of picked items that could actually be found. + // Since a referenced item could potentially be restored later on, instead of changing the selected values here based + // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently + // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending + // on whether it is simply resaved or not. + // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders + // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. + + medias = _.map(ids, + function(id) { + var found = _.find(medias, + function(m) { + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); + }); + if (found) { + return found; + } else { + return { + name: localizationService.dictionary.mediaPicker_deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); + + _.each(medias, + function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + }); + } + } + + setupViewModel(); + + $scope.remove = function(index) { + $scope.mediaItems.splice(index, 1); + $scope.ids.splice(index, 1); + $scope.sync(); + }; + + $scope.goToItem = function(item) { + $location.path('media/media/edit/' + item.id); + }; + + $scope.add = function() { + + $scope.mediaPickerOverlay = { + view: "mediapicker", + title: "Select media", + startNodeId: $scope.model.config.startNodeId, + startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + multiPicker: multiPicker, + onlyImages: onlyImages, + disableFolderSelect: disableFolderSelect, + show: true, + submit: function(model) { + + _.each(model.selectedImages, function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } + else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }; + + $scope.sortableOptions = { + disabled: !$scope.isMultiPicker, + items: "li:not(.add-wrapper)", + cancel: ".unsortable", + update: function(e, ui) { + var r = []; + // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the + // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the + // watch do all the rest. + $timeout(function(){ + angular.forEach($scope.mediaItems, function(value, key) { + r.push($scope.model.config.idType === "udi" ? value.udi : value.id); + }); + $scope.ids = r; + $scope.sync(); + }, 500, false); + } + }; + + $scope.sync = function() { + $scope.model.value = $scope.ids.join(); + }; + + $scope.showAdd = function () { + if (!multiPicker) { + if ($scope.model.value && $scope.model.value !== "") { + return false; + } + } + return true; + }; + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index c7e67a0a42..124f342741 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,11 +67,12 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function (model) { if (model.target.url || model.target.anchor) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 6047169c54..26c6a0c051 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -1,242 +1,242 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService, iconHelper) { - - if (!$scope.model.value) { - $scope.model.value = []; - } - - $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; - - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - $scope.addExternal = true; - $scope.currentEditLink = null; - $scope.hasError = false; - - $scope.internal = function($event) { - $scope.currentEditLink = null; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - }; - - $scope.selectInternal = function ($event, link) { - $scope.currentEditLink = link; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - - }; - - $scope.edit = function (idx) { - for (var i = 0; i < $scope.model.value.length; i++) { - $scope.model.value[i].edit = false; - } - $scope.model.value[idx].edit = true; - }; - - $scope.saveEdit = function (idx) { - $scope.model.value[idx].title = $scope.model.value[idx].caption; - $scope.model.value[idx].edit = false; - }; - - $scope.delete = function (idx) { - $scope.model.value.splice(idx, 1); - }; - - $scope.add = function ($event) { - if (!angular.isArray($scope.model.value)) { - $scope.model.value = []; - } - - if ($scope.newCaption == "") { - $scope.hasError = true; - } else { - if ($scope.addExternal) { - var newExtLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newLink; - this.newWindow = $scope.newNewWindow; - this.edit = false; - this.isInternal = false; - this.type = "external"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newExtLink); - } else { - var newIntLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newInternal; - this.newWindow = $scope.newNewWindow; - this.internal = $scope.newInternal; - this.edit = false; - this.isInternal = true; - this.internalName = $scope.newInternalName; - this.internalIcon = $scope.newInternalIcon; - this.type = "internal"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newIntLink); - } - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - } - $event.preventDefault(); - }; - - $scope.switch = function ($event) { - $scope.addExternal = !$scope.addExternal; - $event.preventDefault(); - }; - - $scope.switchLinkType = function ($event, link) { - link.isInternal = !link.isInternal; - link.type = link.isInternal ? "internal" : "external"; - if (!link.isInternal) - link.link = $scope.newLink; - $event.preventDefault(); - }; - - $scope.move = function (index, direction) { - var temp = $scope.model.value[index]; - $scope.model.value[index] = $scope.model.value[index + direction]; - $scope.model.value[index + direction] = temp; - }; - - //helper for determining if a user can add items - $scope.canAdd = function () { - return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); - } - - //helper that returns if an item can be sorted - $scope.canSort = function () { - return countVisible() > 1; - } - - $scope.sortableOptions = { - axis: 'y', - handle: '.handle', - cursor: 'move', - cancel: '.no-drag', - containment: 'parent', - placeholder: 'sortable-placeholder', - forcePlaceholderSize: true, - helper: function (e, ui) { - // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ - ui.children().each(function () { - $(this).width($(this).width()); - }); - return ui; - }, - items: '> tr:not(.unsortable)', - tolerance: 'pointer', - update: function (e, ui) { - // Get the new and old index for the moved element (using the URL as the identifier) - var newIndex = ui.item.index(); - var movedLinkUrl = ui.item.attr('data-link'); - var originalIndex = getElementIndexByUrl(movedLinkUrl); - - // Move the element in the model - var movedElement = $scope.model.value[originalIndex]; - $scope.model.value.splice(originalIndex, 1); - $scope.model.value.splice(newIndex, 0, movedElement); - }, - start: function (e, ui) { - //ui.placeholder.html(""); - - // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size - var cellCount = 0; - $('td, th', ui.helper).each(function () { - // For each td or th try and get it's colspan attribute, and add that or 1 to the total - var colspan = 1; - var colspanAttr = $(this).attr('colspan'); - if (colspanAttr > 1) { - colspan = colspanAttr; - } - cellCount += colspan; - }); - - // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr - ui.placeholder.html('').height(ui.item.height()); - } - }; - - //helper to count what is visible - function countVisible() { - return $scope.model.value.length; - } - - function isNumeric(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - } - - function getElementIndexByUrl(url) { - for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].link == url) { - return i; - } - } - - return -1; - } - - function select(data) { - if ($scope.currentEditLink != null) { - $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.currentEditLink.internalName = data.name; - $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); - $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; - } else { - $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.newInternalName = data.name; - $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); - } - } - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RelatedLinksController", + function ($rootScope, $scope, dialogService, iconHelper) { + + if (!$scope.model.value) { + $scope.model.value = []; + } + + $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; + + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + $scope.addExternal = true; + $scope.currentEditLink = null; + $scope.hasError = false; + + $scope.internal = function($event) { + $scope.currentEditLink = null; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + }; + + $scope.selectInternal = function ($event, link) { + $scope.currentEditLink = link; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + + }; + + $scope.edit = function (idx) { + for (var i = 0; i < $scope.model.value.length; i++) { + $scope.model.value[i].edit = false; + } + $scope.model.value[idx].edit = true; + }; + + $scope.saveEdit = function (idx) { + $scope.model.value[idx].title = $scope.model.value[idx].caption; + $scope.model.value[idx].edit = false; + }; + + $scope.delete = function (idx) { + $scope.model.value.splice(idx, 1); + }; + + $scope.add = function ($event) { + if (!angular.isArray($scope.model.value)) { + $scope.model.value = []; + } + + if ($scope.newCaption == "") { + $scope.hasError = true; + } else { + if ($scope.addExternal) { + var newExtLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newLink; + this.newWindow = $scope.newNewWindow; + this.edit = false; + this.isInternal = false; + this.type = "external"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newExtLink); + } else { + var newIntLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newInternal; + this.newWindow = $scope.newNewWindow; + this.internal = $scope.newInternal; + this.edit = false; + this.isInternal = true; + this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; + this.type = "internal"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newIntLink); + } + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + } + $event.preventDefault(); + }; + + $scope.switch = function ($event) { + $scope.addExternal = !$scope.addExternal; + $event.preventDefault(); + }; + + $scope.switchLinkType = function ($event, link) { + link.isInternal = !link.isInternal; + link.type = link.isInternal ? "internal" : "external"; + if (!link.isInternal) + link.link = $scope.newLink; + $event.preventDefault(); + }; + + $scope.move = function (index, direction) { + var temp = $scope.model.value[index]; + $scope.model.value[index] = $scope.model.value[index + direction]; + $scope.model.value[index + direction] = temp; + }; + + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); + } + + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + } + + $scope.sortableOptions = { + axis: 'y', + handle: '.handle', + cursor: 'move', + cancel: '.no-drag', + containment: 'parent', + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, + helper: function (e, ui) { + // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + items: '> tr:not(.unsortable)', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the URL as the identifier) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + + // Move the element in the model + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + }, + start: function (e, ui) { + //ui.placeholder.html(""); + + // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(ui.item.height()); + } + }; + + //helper to count what is visible + function countVisible() { + return $scope.model.value.length; + } + + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + function getElementIndexByUrl(url) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].link == url) { + return i; + } + } + + return -1; + } + + function select(data) { + if ($scope.currentEditLink != null) { + $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); + $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; + } else { + $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index f8dea2ee8b..cadbc5f57a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,403 +1,402 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { - - $scope.isLoading = true; - - //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias - // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because - // we have this mini content editor panel that can be launched with MNTP. - var d = new Date(); - var n = d.getTime(); - $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; - - function syncContent(editor){ - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - - //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // the angular bits because tinymce replaces the textarea. - angularHelper.getCurrentForm($scope).$setDirty(); - } - - tinyMceService.configuration().then(function (tinyMceConfig) { - - //config value from general tinymce.config file - var validElements = tinyMceConfig.validElements; - - //These are absolutely required in order for the macros to render inline - //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; - - var invalidElements = tinyMceConfig.inValidElements; - var plugins = _.map(tinyMceConfig.plugins, function (plugin) { - if (plugin.useOnFrontend) { - return plugin.name; - } - }).join(" "); - - var editorConfig = $scope.model.config.editor; - if (!editorConfig || angular.isString(editorConfig)) { - editorConfig = tinyMceService.defaultPrevalues(); - } - - //config value on the data type - var toolbar = editorConfig.toolbar.join(" | "); - var stylesheets = []; - var styleFormats = []; - var await = []; - if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { - editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; - } - - //queue file loading - if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); - } - - //queue rules loading - angular.forEach(editorConfig.stylesheets, function (val, key) { - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); - await.push(stylesheetResource.getRulesByName(val).then(function (rules) { - angular.forEach(rules, function (rule) { - var r = {}; - r.title = rule.name; - if (rule.selector[0] == ".") { - r.inline = "span"; - r.classes = rule.selector.substring(1); - } - else if (rule.selector[0] == "#") { - r.inline = "span"; - r.attributes = { id: rule.selector.substring(1) }; - } - else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { - var split = rule.selector.split("."); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - } - else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { - var split = rule.selector.split("#"); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); - } - else { - r.block = rule.selector; - } - - styleFormats.push(r); - }); - })); - }); - - - //stores a reference to the editor - var tinyMceEditor = null; - - // these languages are available for localization - var availableLanguages = [ - 'da', - 'de', - 'en', - 'en_us', - 'fi', - 'fr', - 'he', - 'it', - 'ja', - 'nl', - 'no', - 'pl', - 'pt', - 'ru', - 'sv', - 'zh' - ]; - - //define fallback language - var language = 'en_us'; - //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. - //wheras tinymce is in the format of ru, de, en, en_us, etc. - var localeId = $locale.id.replace('-', '_'); - //try matching the language using full locale format - var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); - //if no matches, try matching using only the language - if (languageMatch === undefined) { - var localeParts = localeId.split('_'); - languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); - } - //if a match was found - set the language - if (languageMatch !== undefined) { - language = languageMatch; - } - - //wait for queue to end - $q.all(await).then(function () { - - //create a baseline Config to exten upon - var baseLineConfigObj = { - mode: "exact", - skin: "umbraco", - plugins: plugins, - valid_elements: validElements, - invalid_elements: invalidElements, - extended_valid_elements: extendedValidElements, - menubar: false, - statusbar: false, - relative_urls: false, - height: editorConfig.dimensions.height, - width: editorConfig.dimensions.width, - maxImageSize: editorConfig.maxImageSize, - toolbar: toolbar, - content_css: stylesheets, - style_formats: styleFormats, - language: language, - //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix - cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster - }; - - if (tinyMceConfig.customConfig) { - - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to - // convert it to json instead of having it as a string since this is what tinymce requires - for (var i in tinyMceConfig.customConfig) { - var val = tinyMceConfig.customConfig[i]; - if (val) { - val = val.toString().trim(); - if (val.detectIsJson()) { - try { - tinyMceConfig.customConfig[i] = JSON.parse(val); - //now we need to check if this custom config key is defined in our baseline, if it is we don't want to - //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise - //if it's an object it will overwrite the baseline - if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { - //concat it and below this concat'd array will overwrite the baseline in angular.extend - tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); - } - } - catch (e) { - //cannot parse, we'll just leave it - } - } - if (val === "true") { - tinyMceConfig.customConfig[i] = true; - } - if (val === "false") { - tinyMceConfig.customConfig[i] = false; - } - } - } - - angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); - } - - //set all the things that user configs should not be able to override - baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! - baseLineConfigObj.setup = function (editor) { - - //set the reference - tinyMceEditor = editor; - - //enable browser based spell checking - editor.on('init', function (e) { - editor.getBody().setAttribute('spellcheck', true); - }); - - //We need to listen on multiple things here because of the nature of tinymce, it doesn't - //fire events when you think! - //The change event doesn't fire when content changes, only when cursor points are changed and undo points - //are created. the blur event doesn't fire if you insert content into the editor with a button and then - //press save. - //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can - //listen to both change and blur and also on our own 'saving' event. I think this will be best because a - //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked - //save before the timeout elapsed. - - //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce - // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers - // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. - // see: http://issues.umbraco.org/issue/U4-4485 - //var alreadyDirty = false; - //editor.on('change', function (e) { - // angularHelper.safeApply($scope, function () { - // $scope.model.value = editor.getContent(); - - // if (!alreadyDirty) { - // //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // // the angular bits because tinymce replaces the textarea. - // var currForm = angularHelper.getCurrentForm($scope); - // currForm.$setDirty(); - // alreadyDirty = true; - // } - - // }); - //}); - - //when we leave the editor (maybe) - editor.on('blur', function (e) { - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - }); - - //when buttons modify content - editor.on('ExecCommand', function (e) { - syncContent(editor); - }); - - // Update model on keypress - editor.on('KeyUp', function (e) { - syncContent(editor); - }); - - // Update model on change, i.e. copy/pasted text, plugins altering content - editor.on('SetContent', function (e) { - if (!e.initial) { - syncContent(editor); - } - }); - - - editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; - var srcAttr = $(e.target).attr("src"); - var path = srcAttr.split("?")[0]; - $(e.target).attr("data-mce-src", path + qs); - - syncContent(editor); - }); - - tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; - }); - - //Create the insert media plugin - tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ - var ignoreUserStartNodes = false; - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - - if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; - startNodeId = -1; - startNodeIsVirtual = true; - } - - $scope.mediaPickerOverlay = { - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - disableFolderSelect: true, - startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - view: "mediapicker", - show: true, - submit: function(model) { - tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - - }); - - //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { - - $scope.embedOverlay = { - view: "embed", - show: true, - submit: function(model) { - tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); - $scope.embedOverlay.show = false; - $scope.embedOverlay = null; - } - }; - - }); - - - //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { - - $scope.macroPickerOverlay = { - view: "macropicker", - dialogData: dialogData, - show: true, - submit: function(model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - tinyMceService.insertMacroInEditor(editor, macroObject, $scope); - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - } - }; - - }); - }; - - /** Loads in the editor */ - function loadTinyMce() { - - //we need to add a timeout here, to force a redraw so TinyMCE can find - //the elements needed - $timeout(function () { - tinymce.DOM.events.domLoaded = true; - tinymce.init(baseLineConfigObj); - - $scope.isLoading = false; - - }, 200, false); - } - - - - - loadTinyMce(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server; - //uses an empty string in the editor when the value is null - tinyMceEditor.setContent(newVal || "", { format: 'raw' }); - //we need to manually fire this event since it is only ever fired based on loading from the DOM, this - // is required for our plugins listening to this event to execute - tinyMceEditor.fire('LoadContent', null); - }; - - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var unsubscribe = $scope.$on("formSubmitting", function () { - //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer - // we do parse it out on the server side but would be nice to do that on the client side before as well. - if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { - $scope.model.value = tinyMceEditor.getContent(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - $scope.$on('$destroy', function () { - unsubscribe(); - if (tinyMceEditor !== undefined && tinyMceEditor != null) { - tinyMceEditor.destroy(); - } - }); - }); - }); - - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RTEController", + function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + + $scope.isLoading = true; + + //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias + // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because + // we have this mini content editor panel that can be launched with MNTP. + var d = new Date(); + var n = d.getTime(); + $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; + + function syncContent(editor){ + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + + //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // the angular bits because tinymce replaces the textarea. + angularHelper.getCurrentForm($scope).$setDirty(); + } + + tinyMceService.configuration().then(function (tinyMceConfig) { + + //config value from general tinymce.config file + var validElements = tinyMceConfig.validElements; + + //These are absolutely required in order for the macros to render inline + //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; + + var invalidElements = tinyMceConfig.inValidElements; + var plugins = _.map(tinyMceConfig.plugins, function (plugin) { + if (plugin.useOnFrontend) { + return plugin.name; + } + }).join(" "); + + var editorConfig = $scope.model.config.editor; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + } + + //config value on the data type + var toolbar = editorConfig.toolbar.join(" | "); + var stylesheets = []; + var styleFormats = []; + var await = []; + if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; + } + + //queue file loading + if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded + await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); + } + + //queue rules loading + angular.forEach(editorConfig.stylesheets, function (val, key) { + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); + await.push(stylesheetResource.getRulesByName(val).then(function (rules) { + angular.forEach(rules, function (rule) { + var r = {}; + r.title = rule.name; + if (rule.selector[0] == ".") { + r.inline = "span"; + r.classes = rule.selector.substring(1); + } + else if (rule.selector[0] == "#") { + r.inline = "span"; + r.attributes = { id: rule.selector.substring(1) }; + } + else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { + var split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + } + else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { + var split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); + } + else { + r.block = rule.selector; + } + + styleFormats.push(r); + }); + })); + }); + + + //stores a reference to the editor + var tinyMceEditor = null; + + // these languages are available for localization + var availableLanguages = [ + 'da', + 'de', + 'en', + 'en_us', + 'fi', + 'fr', + 'he', + 'it', + 'ja', + 'nl', + 'no', + 'pl', + 'pt', + 'ru', + 'sv', + 'zh' + ]; + + //define fallback language + var language = 'en_us'; + //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. + //wheras tinymce is in the format of ru, de, en, en_us, etc. + var localeId = $locale.id.replace('-', '_'); + //try matching the language using full locale format + var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); + //if no matches, try matching using only the language + if (languageMatch === undefined) { + var localeParts = localeId.split('_'); + languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); + } + //if a match was found - set the language + if (languageMatch !== undefined) { + language = languageMatch; + } + + //wait for queue to end + $q.all(await).then(function () { + + //create a baseline Config to exten upon + var baseLineConfigObj = { + mode: "exact", + skin: "umbraco", + plugins: plugins, + valid_elements: validElements, + invalid_elements: invalidElements, + extended_valid_elements: extendedValidElements, + menubar: false, + statusbar: false, + relative_urls: false, + height: editorConfig.dimensions.height, + width: editorConfig.dimensions.width, + maxImageSize: editorConfig.maxImageSize, + toolbar: toolbar, + content_css: stylesheets, + style_formats: styleFormats, + language: language, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster + }; + + if (tinyMceConfig.customConfig) { + + //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to + // convert it to json instead of having it as a string since this is what tinymce requires + for (var i in tinyMceConfig.customConfig) { + var val = tinyMceConfig.customConfig[i]; + if (val) { + val = val.toString().trim(); + if (val.detectIsJson()) { + try { + tinyMceConfig.customConfig[i] = JSON.parse(val); + //now we need to check if this custom config key is defined in our baseline, if it is we don't want to + //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise + //if it's an object it will overwrite the baseline + if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { + //concat it and below this concat'd array will overwrite the baseline in angular.extend + tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); + } + } + catch (e) { + //cannot parse, we'll just leave it + } + } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } + } + } + + angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); + } + + //set all the things that user configs should not be able to override + baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! + baseLineConfigObj.setup = function (editor) { + + //set the reference + tinyMceEditor = editor; + + //enable browser based spell checking + editor.on('init', function (e) { + editor.getBody().setAttribute('spellcheck', true); + }); + + //We need to listen on multiple things here because of the nature of tinymce, it doesn't + //fire events when you think! + //The change event doesn't fire when content changes, only when cursor points are changed and undo points + //are created. the blur event doesn't fire if you insert content into the editor with a button and then + //press save. + //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can + //listen to both change and blur and also on our own 'saving' event. I think this will be best because a + //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked + //save before the timeout elapsed. + + //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce + // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers + // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. + // see: http://issues.umbraco.org/issue/U4-4485 + //var alreadyDirty = false; + //editor.on('change', function (e) { + // angularHelper.safeApply($scope, function () { + // $scope.model.value = editor.getContent(); + + // if (!alreadyDirty) { + // //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // // the angular bits because tinymce replaces the textarea. + // var currForm = angularHelper.getCurrentForm($scope); + // currForm.$setDirty(); + // alreadyDirty = true; + // } + + // }); + //}); + + //when we leave the editor (maybe) + editor.on('blur', function (e) { + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + }); + + //when buttons modify content + editor.on('ExecCommand', function (e) { + syncContent(editor); + }); + + // Update model on keypress + editor.on('KeyUp', function (e) { + syncContent(editor); + }); + + // Update model on change, i.e. copy/pasted text, plugins altering content + editor.on('SetContent', function (e) { + if (!e.initial) { + syncContent(editor); + } + }); + + + editor.on('ObjectResized', function (e) { + var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; + var srcAttr = $(e.target).attr("src"); + var path = srcAttr.split("?")[0]; + $(e.target).attr("data-mce-src", path + qs); + + syncContent(editor); + }); + + tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + + //Create the insert media plugin + tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + startNodeId = -1; + startNodeIsVirtual = true; + } + + $scope.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + disableFolderSelect: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + view: "mediapicker", + show: true, + submit: function(model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + + }); + + //Create the embedded plugin + tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { + + $scope.embedOverlay = { + view: "embed", + show: true, + submit: function(model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + $scope.embedOverlay.show = false; + $scope.embedOverlay = null; + } + }; + + }); + + + //Create the insert macro plugin + tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { + + $scope.macroPickerOverlay = { + view: "macropicker", + dialogData: dialogData, + show: true, + submit: function(model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + } + }; + + }); + }; + + /** Loads in the editor */ + function loadTinyMce() { + + //we need to add a timeout here, to force a redraw so TinyMCE can find + //the elements needed + $timeout(function () { + tinymce.DOM.events.domLoaded = true; + tinymce.init(baseLineConfigObj); + + $scope.isLoading = false; + + }, 200, false); + } + + + + + loadTinyMce(); + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server; + //uses an empty string in the editor when the value is null + tinyMceEditor.setContent(newVal || "", { format: 'raw' }); + //we need to manually fire this event since it is only ever fired based on loading from the DOM, this + // is required for our plugins listening to this event to execute + tinyMceEditor.fire('LoadContent', null); + }; + + //listen for formSubmitting event (the result is callback used to remove the event subscription) + var unsubscribe = $scope.$on("formSubmitting", function () { + //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer + // we do parse it out on the server side but would be nice to do that on the client side before as well. + if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { + $scope.model.value = tinyMceEditor.getContent(); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + $scope.$on('$destroy', function () { + unsubscribe(); + if (tinyMceEditor !== undefined && tinyMceEditor != null) { + tinyMceEditor.destroy(); + } + }); + }); + }); + + }); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b8d9c8bcb6..c909c1eb84 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1,1227 +1,1227 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Events; -using Constants = Umbraco.Core.Constants; -using umbraco.cms.businesslogic; -using System.Collections; -using umbraco; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for editing content - /// - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the content application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [ContentControllerConfiguration] - public class ContentController : ContentControllerBase - { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Configures this controller with a custom action selector - /// - private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) - )); - } - } - - /// - /// Return content for the specified ids - /// - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); - } - - /// - /// Updates the permissions for a content item for a particular user group - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to update - /// - [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] - public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) - { - if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be alowed to set granular permissions? - - var content = Services.ContentService.GetById(saveModel.ContentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //current permissions explicitly assigned to this content item - var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) - .ToDictionary(x => x.UserGroupId, x => x); - - var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); - - //loop through each user group - foreach (var userGroup in allUserGroups) - { - //check if there's a permission set posted up for this user group - IEnumerable groupPermissions; - if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) - { - //create a string collection of the assigned letters - var groupPermissionCodes = groupPermissions.ToArray(); - - //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions - //for this group/node which will go back to the defaults - if (groupPermissionCodes.Length == 0) - { - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored - else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) - { - //only remove them if they are actually currently assigned - if (contentPermissions.ContainsKey(userGroup.Id)) - { - //remove these permissions from this node for this group since the ones being assigned are the same as the defaults - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - } - //if they are different we need to update, otherwise there's nothing to update - else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) - { - - Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); - } - } - } - - return GetDetailedPermissions(content, allUserGroups); - } - - /// - /// Returns the user group permissions for user groups assigned to this node - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to view - /// - [EnsureUserPermissionForContent("contentId", 'R')] - public IEnumerable GetDetailedPermissions(int contentId) - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be able to see detailed permissions? - - var allUserGroups = Services.UserService.GetAllUserGroups(); - - return GetDetailedPermissions(content, allUserGroups); - } - - private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) - { - //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. - //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. - - var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); - - var defaultPermissionsAsDictionary = defaultPermissionsByGroup - .ToDictionary(x => Convert.ToInt32(x.Id), x => x); - - //get the actual assigned permissions - var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); - - //iterate over assigned and update the defaults with the real values - foreach (var assignedGroupPermission in assignedPermissionsByGroup) - { - var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; - - //clone the default permissions model to the assigned ones - defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); - - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones - foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) - { - permission.Checked = false; - permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); - } - - } - - return defaultPermissionsByGroup; - } - - /// - /// Returns an item to be used to display the recycle bin for content - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinContent, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinContent - }; - - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); - - return display; - } - - public ContentItemDisplay GetBlueprintById(int id) - { - var foundContent = Services.ContentService.GetBlueprintById(id); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - SetupBlueprint(content, foundContent); - - return content; - } - - private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) - { - content.AllowPreview = false; - - //set a custom path since the tree that renders this has the content type id as the parent - content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); - - content.AllowedActions = new[] { "A" }; - content.IsBlueprint = true; - - var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; - var propsTab = content.Tabs.Last(); - propsTab.Properties = propsTab.Properties - .Where(p => excludeProps.Contains(p.Alias) == false); - } - - /// - /// Gets the content json for the content id - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetWithTreeDefinition(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - /// If this is a container type, we'll remove the umbContainerView tab for a new item since - /// it cannot actually list children if it doesn't exist yet. - /// - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - // translate the content type name if applicable - mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); - // if your user type doesn't have access to the Settings section it would not get this property mapped - if(mapped.DocumentType != null) - mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(int blueprintId, int parentId) - { - var blueprint = Services.ContentService.GetBlueprintById(blueprintId); - if (blueprint == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - blueprint.Id = 0; - blueprint.Name = string.Empty; - blueprint.ParentId = parentId; - - var mapped = Mapper.Map(blueprint); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(int id) - { - var url = Umbraco.NiceUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Guid id) - { - var url = Umbraco.UrlProvider.GetUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetNiceUrl(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - string includeProperties, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - long totalChildren; - IContent[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.ContentService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); - } - else - { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children.Select(content => - Mapper.Map>(content, - opts => - { - // if there's a list of property aliases to map - we will make sure to store this in the mapping context. - if (String.IsNullOrWhiteSpace(includeProperties) == false) - { - opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); - } - })); - - return pagedResult; - } - - [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] - public bool GetHasPermission(string permissionToCheck, int nodeId) - { - return HasPermission(permissionToCheck, nodeId); - } - - /// - /// Returns permissions for all nodes passed in for the current user - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - [HttpPost] - public Dictionary GetPermissions(int[] nodeIds) - { - var permissions = Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds); - - var permissionsDictionary = new Dictionary(); - foreach (var nodeId in nodeIds) - { - var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); - permissionsDictionary.Add(nodeId, aggregatePerms); - } - - return permissionsDictionary; - } - - /// - /// Checks a nodes permission for the current user - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - /// - [HttpGet] - public bool HasPermission(string permissionToCheck, int nodeId) - { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); - if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) - { - return true; - } - - return false; - } - - /// - /// Creates a blueprint from a content item - /// - /// The content id to copy - /// The name of the blueprint - /// - [HttpPost] - public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - - var content = Services.ContentService.GetById(contentId); - if (content == null) - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - EnsureUniqueName(name, content, "name"); - - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); - - Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); - - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddSuccessNotification( - Services.TextService.Localize("blueprints/createdBlueprintHeading"), - Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) - ); - - return notificationModel; - } - - private void EnsureUniqueName(string name, IContent content, string modelName) - { - var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); - if (existing.Any(x => x.Name == name && x.Id != content.Id)) - { - ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) - { - var contentItemDisplay = PostSaveInternal(contentItem, - content => - { - EnsureUniqueName(content.Name, content, "Name"); - - Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); - //we need to reuse the underlying logic so return the result that it wants - return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); - }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); - - return contentItemDisplay; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - [OutgoingEditorModelEvent] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { - return PostSaveInternal(contentItem, - content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); - } - - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw a validation message - var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - - } - - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) - { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; - } - } - - //initialize this to successful - var publishStatus = Attempt.Succeed(); - var wasCancelled = false; - - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) - { - //save the item - var saveResult = saveMethod(contentItem.PersistedContent); - - wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) - { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - //publish the item and check if it worked, if not we will show a diff msg below - publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; - } - - //return the updated model - var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - contentItem.ReleaseDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) - : Services.TextService.Localize("speechBubbles/editContentSavedText") - ); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate); - break; - } - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - - display.PersistedContent = contentItem.PersistedContent; - - return display; - } - - /// - /// Publishes a document with a given ID - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Publish access to this node. - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public HttpResponseMessage PostPublishById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); - if (publishResult.Success == false) - { - var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); - return Request.CreateValidationErrorResponse(notificationModel); - } - - //return ok - return Request.CreateResponse(HttpStatusCode.OK); - - } - - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteBlueprint(int id) - { - var found = Services.ContentService.GetBlueprintById(id); - - if (found == null) - { - return HandleContentNotFound(id, false); - } - - Services.ContentService.DeleteBlueprint(found); - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Delete access to this node. - /// - [EnsureUserPermissionForContent("id", 'D')] - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundContent.IsInRecycleBin() == false) - { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Empties the recycle bin - /// - /// - /// - /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin - /// - [HttpDelete] - [HttpPost] - [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] - public HttpResponseMessage EmptyRecycleBin() - { - Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// Change the sort order for content - /// - /// - /// - [EnsureUserPermissionForContent("sorted.ParentId", 'S')] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - try - { - var contentService = Services.ContentService; - - // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) - { - LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update content sort order", ex); - throw; - } - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("move.ParentId", 'M')] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - - Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Copies a content item and places the copy as a child of a given parent Id - /// - /// - /// - [EnsureUserPermissionForContent("copy.ParentId", 'C')] - public HttpResponseMessage PostCopy(MoveOrCopy copy) - { - var toCopy = ValidateMoveOrCopy(copy); - - var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Unpublishes a node with a given Id and returns the unpublished entity - /// - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - [OutgoingEditorModelEvent] - public ContentItemDisplay PostUnPublish(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - HandleContentNotFound(id); - - var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - if (unpublishResult == false) - { - AddCancelMessage(content); - throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); - } - else - { - content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); - return content; - } - } - - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); - - //TODO: We need to support 'send to publish' - - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } - - base.MapPropertyValues(contentItem); - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IContent ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var contentService = Services.ContentService; - var toMove = contentService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); - } - } - else - { - var parent = contentService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); - } - } - - return toMove; - } - - private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate) - { - switch (status.StatusType) - { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - expireDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) - : Services.TextService.Localize("speechBubbles/editContentPublishedText") - ); - break; - case PublishStatusType.FailedPathNotPublished: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedCancelledByEvent: - AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; - case PublishStatusType.FailedAwaitingRelease: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedHasExpired: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); - break; - case PublishStatusType.FailedIsTrashed: - //TODO: We should add proper error messaging for this! - break; - case PublishStatusType.FailedContentInvalid: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); - break; - default: - throw new IndexOutOfRangeException(); - } - } - - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// - /// Specifies the already resolved content item to check against - /// If set to true, user and group start node permissions will be ignored. - /// - internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - IEntityService entityService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null, - bool ignoreUserStartNodes = false) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (userService == null) throw new ArgumentNullException("userService"); - if (contentService == null) throw new ArgumentNullException("contentService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - contentItem = contentService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IContent).ToString()] = contentItem; - } - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if(ignoreUserStartNodes) - { - return true; - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasContentRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinContent) - ? user.HasContentBinAccess(entityService) - : user.HasPathAccess(contentItem, entityService); - - if (hasPathAccess == false) - { - return false; - } - - if (permissionsToCheck == null || permissionsToCheck.Length == 0) - { - return true; - } - - //get the implicit/inherited permissions for the user for this path, - //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) - var path = contentItem != null ? contentItem.Path : nodeId.ToString(); - var permission = userService.GetPermissionsForPath(user, path); - - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null - || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; - } - - [EnsureUserPermissionForContent("contentId", 'F')] - public IEnumerable GetNotificationOptions(int contentId) - { - var notifications = new List(); - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var actionList = ActionsResolver.Current.Actions; - foreach (var a in actionList) - { - if (a.ShowInNotifier) - { - NotifySetting n = new NotifySetting - { - Name = ui.Text("actions", a.Alias), - Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), - NotifyCode = a.Letter.ToString() - }; - notifications.Add(n); - } - } - return notifications; - } - - public void PostNotificationOptions(int contentId, string notifyOptions = "") - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var node = new CMSNode(contentId); - - global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Events; +using Constants = Umbraco.Core.Constants; +using umbraco.cms.businesslogic; +using System.Collections; +using umbraco; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for editing content + /// + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the content application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [ContentControllerConfiguration] + public class ContentController : ContentControllerBase + { + /// + /// Constructor + /// + public ContentController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public ContentController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Configures this controller with a custom action selector + /// + private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) + )); + } + } + + /// + /// Return content for the specified ids + /// + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundContent = Services.ContentService.GetByIds(ids); + return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); + } + + /// + /// Updates the permissions for a content item for a particular user group + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to update + /// + [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] + public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) + { + if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be alowed to set granular permissions? + + var content = Services.ContentService.GetById(saveModel.ContentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //current permissions explicitly assigned to this content item + var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) + .ToDictionary(x => x.UserGroupId, x => x); + + var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); + + //loop through each user group + foreach (var userGroup in allUserGroups) + { + //check if there's a permission set posted up for this user group + IEnumerable groupPermissions; + if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) + { + //create a string collection of the assigned letters + var groupPermissionCodes = groupPermissions.ToArray(); + + //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions + //for this group/node which will go back to the defaults + if (groupPermissionCodes.Length == 0) + { + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored + else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) + { + //only remove them if they are actually currently assigned + if (contentPermissions.ContainsKey(userGroup.Id)) + { + //remove these permissions from this node for this group since the ones being assigned are the same as the defaults + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + } + //if they are different we need to update, otherwise there's nothing to update + else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) + { + + Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); + } + } + } + + return GetDetailedPermissions(content, allUserGroups); + } + + /// + /// Returns the user group permissions for user groups assigned to this node + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to view + /// + [EnsureUserPermissionForContent("contentId", 'R')] + public IEnumerable GetDetailedPermissions(int contentId) + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be able to see detailed permissions? + + var allUserGroups = Services.UserService.GetAllUserGroups(); + + return GetDetailedPermissions(content, allUserGroups); + } + + private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) + { + //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. + //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. + + var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); + + var defaultPermissionsAsDictionary = defaultPermissionsByGroup + .ToDictionary(x => Convert.ToInt32(x.Id), x => x); + + //get the actual assigned permissions + var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); + + //iterate over assigned and update the defaults with the real values + foreach (var assignedGroupPermission in assignedPermissionsByGroup) + { + var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; + + //clone the default permissions model to the assigned ones + defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + } + + return defaultPermissionsByGroup; + } + + /// + /// Returns an item to be used to display the recycle bin for content + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinContent, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinContent + }; + + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + + return display; + } + + public ContentItemDisplay GetBlueprintById(int id) + { + var foundContent = Services.ContentService.GetBlueprintById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + SetupBlueprint(content, foundContent); + + return content; + } + + private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) + { + content.AllowPreview = false; + + //set a custom path since the tree that renders this has the content type id as the parent + content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); + + content.AllowedActions = new[] { "A" }; + content.IsBlueprint = true; + + var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; + var propsTab = content.Tabs.Last(); + propsTab.Properties = propsTab.Properties + .Where(p => excludeProps.Contains(p.Alias) == false); + } + + /// + /// Gets the content json for the content id + /// + /// + /// If set used to lookup whether the user and group start node permissions should be ignored. + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(int id, [FromUri]Guid? dataTypeId = null) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + /// If this is a container type, we'll remove the umbContainerView tab for a new item since + /// it cannot actually list children if it doesn't exist yet. + /// + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + // translate the content type name if applicable + mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); + // if your user type doesn't have access to the Settings section it would not get this property mapped + if(mapped.DocumentType != null) + mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(int blueprintId, int parentId) + { + var blueprint = Services.ContentService.GetBlueprintById(blueprintId); + if (blueprint == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + blueprint.Id = 0; + blueprint.Name = string.Empty; + blueprint.ParentId = parentId; + + var mapped = Mapper.Map(blueprint); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(int id) + { + var url = Umbraco.NiceUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Guid id) + { + var url = Umbraco.UrlProvider.GetUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetNiceUrl(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + string includeProperties, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + long totalChildren; + IContent[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.ContentService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children.Select(content => + Mapper.Map>(content, + opts => + { + // if there's a list of property aliases to map - we will make sure to store this in the mapping context. + if (String.IsNullOrWhiteSpace(includeProperties) == false) + { + opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); + } + })); + + return pagedResult; + } + + [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] + public bool GetHasPermission(string permissionToCheck, int nodeId) + { + return HasPermission(permissionToCheck, nodeId); + } + + /// + /// Returns permissions for all nodes passed in for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + [HttpPost] + public Dictionary GetPermissions(int[] nodeIds) + { + var permissions = Services.UserService + .GetPermissions(Security.CurrentUser, nodeIds); + + var permissionsDictionary = new Dictionary(); + foreach (var nodeId in nodeIds) + { + var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); + permissionsDictionary.Add(nodeId, aggregatePerms); + } + + return permissionsDictionary; + } + + /// + /// Checks a nodes permission for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + /// + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) + { + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); + if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + /// + /// Creates a blueprint from a content item + /// + /// The content id to copy + /// The name of the blueprint + /// + [HttpPost] + public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var content = Services.ContentService.GetById(contentId); + if (content == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + EnsureUniqueName(name, content, "name"); + + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); + + Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); + + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddSuccessNotification( + Services.TextService.Localize("blueprints/createdBlueprintHeading"), + Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) + ); + + return notificationModel; + } + + private void EnsureUniqueName(string name, IContent content, string modelName) + { + var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); + if (existing.Any(x => x.Name == name && x.Id != content.Id)) + { + ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSaveBlueprint( + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) + { + var contentItemDisplay = PostSaveInternal(contentItem, + content => + { + EnsureUniqueName(content.Name, content, "Name"); + + Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); + //we need to reuse the underlying logic so return the result that it wants + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); + }); + SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); + + return contentItemDisplay; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + [OutgoingEditorModelEvent] + public ContentItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { + return PostSaveInternal(contentItem, + content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + } + + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw a validation message + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + + } + + //if the model state is not valid we cannot publish so change it to save + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } + } + + //initialize this to successful + var publishStatus = Attempt.Succeed(); + var wasCancelled = false; + + if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + { + //save the item + var saveResult = saveMethod(contentItem.PersistedContent); + + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; + } + else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + { + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; + } + else + { + //publish the item and check if it worked, if not we will show a diff msg below + publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; + } + + //return the updated model + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + contentItem.ReleaseDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) + : Services.TextService.Localize("speechBubbles/editContentSavedText") + ); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.Publish: + case ContentSaveAction.PublishNew: + ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate); + break; + } + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (wasCancelled && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + + display.PersistedContent = contentItem.PersistedContent; + + return display; + } + + /// + /// Publishes a document with a given ID + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Publish access to this node. + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public HttpResponseMessage PostPublishById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + + } + + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteBlueprint(int id) + { + var found = Services.ContentService.GetBlueprintById(id); + + if (found == null) + { + return HandleContentNotFound(id, false); + } + + Services.ContentService.DeleteBlueprint(found); + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Delete access to this node. + /// + [EnsureUserPermissionForContent("id", 'D')] + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundContent.IsInRecycleBin() == false) + { + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Empties the recycle bin + /// + /// + /// + /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin + /// + [HttpDelete] + [HttpPost] + [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// Change the sort order for content + /// + /// + /// + [EnsureUserPermissionForContent("sorted.ParentId", 'S')] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + try + { + var contentService = Services.ContentService; + + // Save content with new sort order and update content xml in db accordingly + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) + { + LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update content sort order", ex); + throw; + } + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("move.ParentId", 'M')] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + + Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Copies a content item and places the copy as a child of a given parent Id + /// + /// + /// + [EnsureUserPermissionForContent("copy.ParentId", 'C')] + public HttpResponseMessage PostCopy(MoveOrCopy copy) + { + var toCopy = ValidateMoveOrCopy(copy); + + var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Unpublishes a node with a given Id and returns the unpublished entity + /// + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + [OutgoingEditorModelEvent] + public ContentItemDisplay PostUnPublish(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + HandleContentNotFound(id); + + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } + + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); + + //TODO: We need to support 'send to publish' + + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } + + base.MapPropertyValues(contentItem); + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IContent ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var contentService = Services.ContentService; + var toMove = contentService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + } + } + else + { + var parent = contentService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + } + } + + return toMove; + } + + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + expireDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) + : Services.TextService.Localize("speechBubbles/editContentPublishedText") + ); + break; + case PublishStatusType.FailedPathNotPublished: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedCancelledByEvent: + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); + break; + case PublishStatusType.FailedAwaitingRelease: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedHasExpired: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); + break; + case PublishStatusType.FailedIsTrashed: + //TODO: We should add proper error messaging for this! + break; + case PublishStatusType.FailedContentInvalid: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); + break; + default: + throw new IndexOutOfRangeException(); + } + } + + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// + /// Specifies the already resolved content item to check against + /// If set to true, user and group start node permissions will be ignored. + /// + internal static bool CheckPermissions( + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + IEntityService entityService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null, + bool ignoreUserStartNodes = false) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (userService == null) throw new ArgumentNullException("userService"); + if (contentService == null) throw new ArgumentNullException("contentService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + contentItem = contentService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IContent).ToString()] = contentItem; + } + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if(ignoreUserStartNodes) + { + return true; + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? user.HasContentRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinContent) + ? user.HasContentBinAccess(entityService) + : user.HasPathAccess(contentItem, entityService); + + if (hasPathAccess == false) + { + return false; + } + + if (permissionsToCheck == null || permissionsToCheck.Length == 0) + { + return true; + } + + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + var path = contentItem != null ? contentItem.Path : nodeId.ToString(); + var permission = userService.GetPermissionsForPath(user, path); + + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } + return allowed; + } + + [EnsureUserPermissionForContent("contentId", 'F')] + public IEnumerable GetNotificationOptions(int contentId) + { + var notifications = new List(); + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var actionList = ActionsResolver.Current.Actions; + foreach (var a in actionList) + { + if (a.ShowInNotifier) + { + NotifySetting n = new NotifySetting + { + Name = ui.Text("actions", a.Alias), + Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), + NotifyCode = a.Letter.ToString() + }; + notifications.Add(n); + } + } + return notifications; + } + + public void PostNotificationOptions(int contentId, string notifyOptions = "") + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var node = new CMSNode(contentId); + + global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); + } + } +} diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index a3f76db4f2..36f4465e96 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,999 +1,1004 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Net; -using System.Text; -using System.Web.Http; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using System.Linq; -using System.Net.Http; -using Umbraco.Core.Models; -using Constants = Umbraco.Core.Constants; -using Examine; -using Umbraco.Web.Dynamics; -using System.Text.RegularExpressions; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using System.Web.Http.Controllers; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Xml; -using Umbraco.Web.Search; -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode - /// - /// - /// Some objects such as macros are not based on CMSNode - /// - [EntityControllerConfiguration] - [PluginController("UmbracoApi")] - public class EntityController : UmbracoAuthorizedJsonController - { - - /// - /// Configures this controller with a custom action selector - /// - private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - - //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" - //id is passed in eventually we'll probably want to support GUID + Udi too - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); - } - } - - private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - /// - /// Returns an Umbraco alias given a string - /// - /// - /// - /// - public dynamic GetSafeAlias(string value, bool camelCase = true) - { - var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); - dynamic returnObj = new System.Dynamic.ExpandoObject(); - returnObj.alias = returnValue; - returnObj.original = value; - returnObj.camelCase = camelCase; - - return returnObj; - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, false, searchFrom); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) - { - //TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? - - if (string.IsNullOrEmpty(query)) - return Enumerable.Empty(); - - return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); - } - - /// - /// Searches for all content that the user is allowed to see (based on their allowed sections) - /// - /// - /// - /// - /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need - /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search - /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. - /// - /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search - /// methods might be used in things like pickers in the content editor. - /// - [HttpGet] - public IDictionary SearchAll(string query) - { - var result = new Dictionary(); - - if (string.IsNullOrEmpty(query)) - return result; - - var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); - - foreach (var searchableTree in searchableTrees) - { - if (allowedSections.Contains(searchableTree.Value.AppAlias)) - { - var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); - if (tree == null) continue; //shouldn't occur - - var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); - var treeAttribute = tree.GetTreeAttribute(); - - long total; - - result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult - { - Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), - TreeAlias = searchableTree.Key, - AppAlias = searchableTree.Value.AppAlias, - JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, - JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName - }; - } - } - return result; - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(int id, UmbracoEntityTypes type) - { - var foundContent = GetResultForId(id, type); - - return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) - { - var foundContent = GetResultForKey(id, type); - - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetPath(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Gets the url of an entity - /// - /// Int id of the entity to fetch URL for - /// The tpye of entity such as Document, Media, Member - /// The URL or path to the item - public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) - { - var returnUrl = string.Empty; - - if (type == UmbracoEntityTypes.Document) - { - var foundUrl = Umbraco.Url(id); - if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") - { - returnUrl = foundUrl; - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - } - - var ancestors = GetAncestors(id, type); - - //if content, skip the first node for replicating NiceUrl defaults - if(type == UmbracoEntityTypes.Document) { - ancestors = ancestors.Skip(1); - } - - returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - - [Obsolete("Use GetyById instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by a xpath query - /// - /// - /// - /// - /// - public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) - { - //TODO: Rename this!!! It's misleading, it should be GetByXPath - - - if (type != UmbracoEntityTypes.Document) - throw new ArgumentException("Get by query is only compatible with enitities of type Document"); - - - var q = ParseXPathQuery(query, nodeContextId); - var node = Umbraco.TypedContentSingleAtXPath(q); - - if (node == null) - return null; - - return GetById(node.Id, type); - } - - //PP: wip in progress on the query parser - private string ParseXPathQuery(string query, int id) - { - return UmbracoXPathPathSyntaxParser.ParseXPathQuery( - xpathExpression: query, - nodeContextId: id, - getPath: nodeid => - { - var ent = Services.EntityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); - }, - publishedContentExists: i => Umbraco.TypedContent(i) != null); - } - - #region GetById - - /// - /// Gets an entity by it's id - /// - /// - /// - /// - public EntityBasic GetById(int id, UmbracoEntityTypes type) - { - return GetResultForId(id, type); - } - - /// - /// Gets an entity by it's key - /// - /// - /// - /// - public EntityBasic GetById(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by it's UDI - /// - /// - /// - /// - public EntityBasic GetById(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetResultForKey(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - #region GetByIds - /// - /// Get entities by integer ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForIds(ids, type); - } - - /// - /// Get entities by GUID ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - /// - /// Get entities by UDIs - /// - /// - /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids. - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if (ids.Length == 0) - { - return Enumerable.Empty(); - } - - //all udi types will need to be the same in this list so we'll determine by the first - //currently we only support GuidIdi for this method - - var guidUdi = ids[0] as GuidUdi; - if (guidUdi != null) - { - return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - [Obsolete("Use GetyByIds instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) - { - return GetResultForChildren(id, type); - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - string id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - int intId; - - if (int.TryParse(id, out intId)) - { - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - Guid guidId; - if (Guid.TryParse(id, out guidId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - Udi udiId; - if (Udi.TryParse(id, out udiId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type - if (id == Constants.Conventions.MemberTypes.AllMembersListId) - { - //the EntityService can search paged members from the root - - intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - //the EntityService cannot search members of a certain type, this is currently not supported and would require - //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search - - long total; - var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); - - return new PagedResult(total, pageNumber, pageSize) - { - Items = searchResult - }; - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - long totalRecords; - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - false, orderBy, orderDirection, filter); - } - - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - bool ignoreUserStartNodes, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - IEnumerable entities; - long totalRecords; - - if (id == Constants.System.Root) - { - // root is special: we reduce it to start nodes - - int[] aids = null; - switch (type) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes - ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) - : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - else - { - entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) - { - return GetResultForAncestors(id, type, false); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) - { - return GetResultForAncestors(id, type, ignoreUserStartNodes); - } - - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) - { - return GetResultForAll(type, postFilter, postFilterParams); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) - { - long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); - } - - - private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - return Services.EntityService.GetChildren(id, objectType.Value) - .WhereNotNull() - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - - if (ignoreUserStartNodes == false) - { - int[] aids = null; - switch (entityType) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) - { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } - } - ids = lids.ToArray(); - } - } - - return ids.Length == 0 - ? Enumerable.Empty() - : Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .OrderBy(x => x.Level) - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - /// - /// Gets the result for the entity list based on the type - /// - /// - /// A string where filter that will filter the results dynamically with linq - optional - /// the parameters to fill in the string where filter - optional - /// - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Should we order this by something ? - var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); - return ExecutePostFilter(entities, postFilter, postFilterParams); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Template: - var templates = Services.FileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); - return filteredTemplates.Select(Mapper.Map); - - case UmbracoEntityTypes.Macro: - //Get all macros from the macro service - var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); - return filteredMacros.Select(Mapper.Map); - - case UmbracoEntityTypes.PropertyType: - - //get all document types, then combine all property types into one list - var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyTypes) - .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredPropertyTypes); - - case UmbracoEntityTypes.PropertyGroup: - - //get all document types, then combine all property types into one list - var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyGroups) - .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredpropertyGroups); - - case UmbracoEntityTypes.User: - - int total; - var users = Services.UserService.GetAll(0, int.MaxValue, out total); - var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredUsers); - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) - { - if (keys.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, keys) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Key); - var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) - { - if (ids.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Id); - var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.GetByKey(key, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.Get(id, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) - { - switch (entityType) - { - case UmbracoEntityTypes.Document: - return UmbracoObjectTypes.Document; - case UmbracoEntityTypes.Media: - return UmbracoObjectTypes.Media; - case UmbracoEntityTypes.MemberType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.MemberGroup: - return UmbracoObjectTypes.MemberGroup; - case UmbracoEntityTypes.ContentItem: - return UmbracoObjectTypes.ContentItem; - case UmbracoEntityTypes.MediaType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.DocumentType: - return UmbracoObjectTypes.DocumentType; - case UmbracoEntityTypes.Stylesheet: - return UmbracoObjectTypes.Stylesheet; - case UmbracoEntityTypes.Member: - return UmbracoObjectTypes.Member; - case UmbracoEntityTypes.DataType: - return UmbracoObjectTypes.DataType; - default: - //There is no UmbracoEntity conversion (things like Macros, Users, etc...) - return null; - } - } - - /// - /// Executes the post filter against a collection of objects - /// - /// - /// - /// - /// - /// - private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) - { - //if a post filter is assigned then try to execute it - if (postFilter.IsNullOrWhiteSpace() == false) - { - return postFilterParams == null - ? entities.AsQueryable().Where(postFilter).ToArray() - : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); - - } - return entities; - } - - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Net; +using System.Text; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using System.Linq; +using System.Net.Http; +using Umbraco.Core.Models; +using Constants = Umbraco.Core.Constants; +using Examine; +using Umbraco.Web.Dynamics; +using System.Text.RegularExpressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System.Web.Http.Controllers; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Xml; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode + /// + /// + /// Some objects such as macros are not based on CMSNode + /// + [EntityControllerConfiguration] + [PluginController("UmbracoApi")] + public class EntityController : UmbracoAuthorizedJsonController + { + + /// + /// Configures this controller with a custom action selector + /// + private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + + //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" + //id is passed in eventually we'll probably want to support GUID + Udi too + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); + } + } + + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + + /// + /// Returns an Umbraco alias given a string + /// + /// + /// + /// + public dynamic GetSafeAlias(string value, bool camelCase = true) + { + var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); + dynamic returnObj = new System.Dynamic.ExpandoObject(); + returnObj.alias = returnValue; + returnObj.original = value; + returnObj.camelCase = camelCase; + + return returnObj; + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, null, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set used to look up whether user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, Guid? dataTypeId, string searchFrom = null) + { + //TODO: Should we restrict search results based on what app the user has access to? + // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + + if (string.IsNullOrEmpty(query)) + return Enumerable.Empty(); + + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); + } + + /// + /// Searches for all content that the user is allowed to see (based on their allowed sections) + /// + /// + /// + /// + /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need + /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search + /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. + /// + /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search + /// methods might be used in things like pickers in the content editor. + /// + [HttpGet] + public IDictionary SearchAll(string query) + { + var result = new Dictionary(); + + if (string.IsNullOrEmpty(query)) + return result; + + var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); + var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); + + foreach (var searchableTree in searchableTrees) + { + if (allowedSections.Contains(searchableTree.Value.AppAlias)) + { + var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); + if (tree == null) continue; //shouldn't occur + + var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); + var treeAttribute = tree.GetTreeAttribute(); + + long total; + + result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult + { + Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), + TreeAlias = searchableTree.Key, + AppAlias = searchableTree.Value.AppAlias, + JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, + JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName + }; + } + } + return result; + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(int id, UmbracoEntityTypes type) + { + var foundContent = GetResultForId(id, type); + + return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) + { + var foundContent = GetResultForKey(id, type); + + return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetPath(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Gets the url of an entity + /// + /// Int id of the entity to fetch URL for + /// The tpye of entity such as Document, Media, Member + /// The URL or path to the item + public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) + { + var returnUrl = string.Empty; + + if (type == UmbracoEntityTypes.Document) + { + var foundUrl = Umbraco.Url(id); + if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") + { + returnUrl = foundUrl; + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + } + + var ancestors = GetAncestors(id, type); + + //if content, skip the first node for replicating NiceUrl defaults + if(type == UmbracoEntityTypes.Document) { + ancestors = ancestors.Skip(1); + } + + returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + + [Obsolete("Use GetyById instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by a xpath query + /// + /// + /// + /// + /// + public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) + { + //TODO: Rename this!!! It's misleading, it should be GetByXPath + + + if (type != UmbracoEntityTypes.Document) + throw new ArgumentException("Get by query is only compatible with enitities of type Document"); + + + var q = ParseXPathQuery(query, nodeContextId); + var node = Umbraco.TypedContentSingleAtXPath(q); + + if (node == null) + return null; + + return GetById(node.Id, type); + } + + //PP: wip in progress on the query parser + private string ParseXPathQuery(string query, int id) + { + return UmbracoXPathPathSyntaxParser.ParseXPathQuery( + xpathExpression: query, + nodeContextId: id, + getPath: nodeid => + { + var ent = Services.EntityService.Get(nodeid); + return ent.Path.Split(',').Reverse(); + }, + publishedContentExists: i => Umbraco.TypedContent(i) != null); + } + + #region GetById + + /// + /// Gets an entity by it's id + /// + /// + /// + /// + public EntityBasic GetById(int id, UmbracoEntityTypes type) + { + return GetResultForId(id, type); + } + + /// + /// Gets an entity by it's key + /// + /// + /// + /// + public EntityBasic GetById(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by it's UDI + /// + /// + /// + /// + public EntityBasic GetById(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetResultForKey(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + #region GetByIds + /// + /// Get entities by integer ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForIds(ids, type); + } + + /// + /// Get entities by GUID ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + /// + /// Get entities by UDIs + /// + /// + /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if (ids.Length == 0) + { + return Enumerable.Empty(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidIdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + [Obsolete("Use GetyByIds instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + { + return GetResultForChildren(id, type); + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + string id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + int intId; + + if (int.TryParse(id, out intId)) + { + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + Guid guidId; + if (Guid.TryParse(id, out guidId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Udi udiId; + if (Udi.TryParse(id, out udiId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type + if (id == Constants.Conventions.MemberTypes.AllMembersListId) + { + //the EntityService can search paged members from the root + + intId = -1; + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + //the EntityService cannot search members of a certain type, this is currently not supported and would require + //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search + + long total; + var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); + + return new PagedResult(total, pageNumber, pageSize) + { + Items = searchResult + }; + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + null, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + Guid? dataTypeId, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + IEnumerable entities; + long totalRecords; + + if (id == Constants.System.Root) + { + // root is special: we reduce it to start nodes + + int[] aids = null; + switch (type) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes + ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) + : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + else + { + entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) + { + return GetResultForAncestors(id, type, null); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId) + { + return GetResultForAncestors(id, type, dataTypeId); + } + + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) + { + return GetResultForAll(type, postFilter, postFilterParams); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) + { + long total; + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); + } + + + private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, Guid? dataTypeId = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (ignoreUserStartNodes == false) + { + int[] aids = null; + switch (entityType) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); + } + } + + return ids.Length == 0 + ? Enumerable.Empty() + : Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .OrderBy(x => x.Level) + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + /// + /// Gets the result for the entity list based on the type + /// + /// + /// A string where filter that will filter the results dynamically with linq - optional + /// the parameters to fill in the string where filter - optional + /// + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Should we order this by something ? + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + return ExecutePostFilter(entities, postFilter, postFilterParams); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Template: + var templates = Services.FileService.GetTemplates(); + var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + return filteredTemplates.Select(Mapper.Map); + + case UmbracoEntityTypes.Macro: + //Get all macros from the macro service + var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); + var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); + return filteredMacros.Select(Mapper.Map); + + case UmbracoEntityTypes.PropertyType: + + //get all document types, then combine all property types into one list + var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyTypes) + .DistinctBy(composition => composition.Alias); + var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredPropertyTypes); + + case UmbracoEntityTypes.PropertyGroup: + + //get all document types, then combine all property types into one list + var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyGroups) + .DistinctBy(composition => composition.Name); + var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredpropertyGroups); + + case UmbracoEntityTypes.User: + + int total; + var users = Services.UserService.GetAll(0, int.MaxValue, out total); + var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredUsers); + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) + { + if (keys.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, keys) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Key); + var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) + { + if (ids.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Id); + var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.GetByKey(key, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.Get(id, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) + { + switch (entityType) + { + case UmbracoEntityTypes.Document: + return UmbracoObjectTypes.Document; + case UmbracoEntityTypes.Media: + return UmbracoObjectTypes.Media; + case UmbracoEntityTypes.MemberType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case UmbracoEntityTypes.ContentItem: + return UmbracoObjectTypes.ContentItem; + case UmbracoEntityTypes.MediaType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.DocumentType: + return UmbracoObjectTypes.DocumentType; + case UmbracoEntityTypes.Stylesheet: + return UmbracoObjectTypes.Stylesheet; + case UmbracoEntityTypes.Member: + return UmbracoObjectTypes.Member; + case UmbracoEntityTypes.DataType: + return UmbracoObjectTypes.DataType; + default: + //There is no UmbracoEntity conversion (things like Macros, Users, etc...) + return null; + } + } + + /// + /// Executes the post filter against a collection of objects + /// + /// + /// + /// + /// + /// + private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) + { + //if a post filter is assigned then try to execute it + if (postFilter.IsNullOrWhiteSpace() == false) + { + return postFilterParams == null + ? entities.AsQueryable().Where(postFilter).ToArray() + : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); + + } + return entities; + } + + } +} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 6c0c293572..788ae3a0c7 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -1,1014 +1,1017 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using System.Linq; -using System.Web.Http.Controllers; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Configuration; -using Umbraco.Web.UI; -using Notification = Umbraco.Web.Models.ContentEditing.Notification; -using Umbraco.Core.Persistence; -using Umbraco.Core.Configuration.UmbracoSettings; - -namespace Umbraco.Web.Editors -{ - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the media application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorize(Constants.Applications.Media)] - [MediaControllerControllerConfiguration] - public class MediaController : ContentControllerBase - { - /// - /// Configures this controller with a custom action selector - /// - private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); - } - } - - /// - /// Constructor - /// - public MediaController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - [OutgoingEditorModelEvent] - public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.CurrentUser.Id); - var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Returns an item to be used to display the recycle bin for media - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinMedia, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinMedia - }; - - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); - - return display; - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); - } - - /// - /// Returns media items known to be of a "Folder" type - /// - /// - /// - [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetChildFolders(int id = -1) - { - //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged - var result = GetChildFolders(id, 1, 500); - return result.Items; - } - - /// - /// Returns a paged result of media items known to be of a "Folder" type - /// - /// - /// - /// - /// - public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) - { - //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" - var folderTypes = Services.ContentTypeService - .GetAllMediaTypes() - .Where(x => x.Alias.EndsWith("Folder")) - .Select(x => x.Id) - .ToArray(); - - if (folderTypes.Length == 0) - { - return new PagedResult>(0, pageNumber, pageSize); - } - - long total; - var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); - - return new PagedResult>(total, pageNumber, pageSize) - { - Items = children.Select(Mapper.Map>) - }; - } - - /// - /// Returns the root media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetRootMedia() - { - //TODO: Add permissions check! - - return Services.MediaService.GetRootMedia() - .Select(Mapper.Map>); - } - - #region GetChildren - - private int[] _userStartNodes; - protected int[] UserStartNodes - { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } - } - - /// - /// Returns the child media objects - using the entity INT id - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - bool ignoreUserStartNodes = false) - { - //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) - { - if (pageNumber > 0) - return new PagedResult>(0, 0, 0); - var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - return new PagedResult>(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(Mapper.Map>) - }; - return pr; - } - - // else proceed as usual - - long totalChildren; - IMedia[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.MediaService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); - } - else - { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); - - return pagedResult; - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var entity = Services.EntityService.GetByKey(id); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - var entity = Services.EntityService.GetByKey(guidUdi.Guid); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] - public PagedResult> GetChildren(string id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - bool ignoreUserStartNodes = false) - { - foreach (var type in new[] { typeof(int), typeof(Guid) }) - { - var parsed = id.TryConvertTo(type); - if (parsed) - { - //oooh magic! will auto select the right overload - return GetChildren((dynamic)parsed.Result); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - [EnsureUserPermissionForMedia("id")] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundMedia == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundMedia.IsInRecycleBin() == false) - { - var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("move.Id")] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - var destinationParentID = move.ParentId; - var sourceParentID = toMove.ParentId; - - var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); - - if (sourceParentID == destinationParentID) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); - } - if (moveResult == false) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - else - { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [MediaPostValidate] - [OutgoingEditorModelEvent] - public MediaItemDisplay PostSave( - [ModelBinder(typeof(MediaItemBinder))] - MediaItemSave contentItem) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) - && (contentItem.Action == ContentSaveAction.SaveNew)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw validation response - var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - } - - //save the item - var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); - - //return the updated model - var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (saveStatus.Success) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editMediaSaved"), - Services.TextService.Localize("speechBubbles/editMediaSavedText")); - } - else - { - AddCancelMessage(display); - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - } - - break; - } - - return display; - } - - /// - /// Maps the property values to the persisted entity - /// - /// - protected override void MapPropertyValues(ContentBaseItemSave contentItem) - { - UpdateName(contentItem); - - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } - - /// - /// Empties the recycle bin - /// - /// - [HttpDelete] - [HttpPost] - public HttpResponseMessage EmptyRecycleBin() - { - Services.MediaService.EmptyRecycleBin(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("sorted.ParentId")] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - var mediaService = base.ApplicationContext.Services.MediaService; - var sortedMedia = new List(); - try - { - sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); - - // Save Media with new sort order and update content xml in db accordingly - if (mediaService.Sort(sortedMedia) == false) - { - LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update media sort order", ex); - throw; - } - } - - public MediaItemDisplay PostAddFolder(PostedFolder folder) - { - var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); - - var mediaService = ApplicationContext.Services.MediaService; - - var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f, Security.CurrentUser.Id); - - return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); - } - - /// - /// Used to submit a media file - /// - /// - /// - /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. - /// - [FileUploadCleanupFilter(false)] - public async Task PostAddFile() - { - if (Request.Content.IsMimeMultipartContent() == false) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } - - var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - - //must have a file - if (result.FileData.Count == 0) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //get the string json from the request - string currentFolderId = result.FormData["currentFolder"]; - int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); - - var tempFiles = new PostedFiles(); - var mediaService = ApplicationContext.Services.MediaService; - - //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { - - var folders = result.FormData["path"].Split('/'); - - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; - - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); - - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); - - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } - - //get the files - foreach (var file in result.FileData) - { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); - var safeFileName = fileName.ToSafeFileName(); - var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - - if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - { - mediaType = Constants.Conventions.MediaTypes.Image; - } - } - else - { - mediaType = result.FormData["contentTypeAlias"]; - } - - var mediaItemName = fileName.ToFriendlyName(); - - var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); - - var fileInfo = new FileInfo(file.LocalFileName); - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(Constants.Conventions.Media.File, fileName, fs); - } - - var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, - message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, - localizeMessage: false); - } - else - { - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); - } - } - else - { - tempFiles.Notifications.Add(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("media/disallowedFileType"), - SpeechBubbleIcon.Warning)); - } - } - - //Different response if this is a 'blueimp' request - if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) - { - var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); - if (origin.Value == "blueimp") - { - return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, - //Don't output the angular xsrf stuff, blue imp doesn't like that - new JsonMediaTypeFormatter()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK, tempFiles); - } - - /// - /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT - /// - /// - /// - /// If true, this will check if the current user has access to the resolved integer parent id - /// and if that check fails an unauthorized exception will occur - /// - /// - private int GetParentIdAsInt(string parentId, bool validatePermissions) - { - int intParentId; - GuidUdi parentUdi; - - // test for udi - if (GuidUdi.TryParse(parentId, out parentUdi)) - { - parentId = parentUdi.Guid.ToString(); - } - - //if it's not an INT then we'll check for GUID - if (int.TryParse(parentId, out intParentId) == false) - { - // if a guid then try to look up the entity - Guid idGuid; - if (Guid.TryParse(parentId, out idGuid)) - { - var entity = Services.EntityService.GetByKey(idGuid); - if (entity != null) - { - intParentId = entity.Id; - } - else - { - throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); - } - } - else - { - throw new HttpResponseException( - Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); - } - } - - //ensure the user has access to this folder by parent id! - if (validatePermissions && CheckPermissions( - new Dictionary(), - Security.CurrentUser, - Services.MediaService, - Services.EntityService, - intParentId) == false) - { - throw new HttpResponseException(Request.CreateResponse( - HttpStatusCode.Forbidden, - new SimpleNotificationModel(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), - SpeechBubbleIcon.Warning)))); - } - - return intParentId; - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IMedia ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var mediaService = Services.MediaService; - var toMove = mediaService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - else - { - var parent = mediaService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - - return toMove; - } - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// Specifies the already resolved content item to check against, setting this ignores the nodeId - /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (mediaService == null) throw new ArgumentNullException("mediaService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - media = mediaService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IMedia).ToString()] = media; - } - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasMediaRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(entityService) - : user.HasPathAccess(media, entityService); - - return hasPathAccess; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using System.Linq; +using System.Web.Http.Controllers; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Configuration; +using Umbraco.Web.UI; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Umbraco.Core.Persistence; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Web.Editors +{ + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the media application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [MediaControllerControllerConfiguration] + public class MediaController : ContentControllerBase + { + /// + /// Configures this controller with a custom action selector + /// + private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); + } + } + + /// + /// Constructor + /// + public MediaController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public MediaController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + [OutgoingEditorModelEvent] + public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, Security.CurrentUser.Id); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Returns an item to be used to display the recycle bin for media + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinMedia, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinMedia + }; + + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + + return display; + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Return media for the specified ids + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundMedia = Services.MediaService.GetByIds(ids); + return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); + } + + /// + /// Returns media items known to be of a "Folder" type + /// + /// + /// + [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged + var result = GetChildFolders(id, 1, 500); + return result.Items; + } + + /// + /// Returns a paged result of media items known to be of a "Folder" type + /// + /// + /// + /// + /// + public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) + { + //Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... + //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + var folderTypes = Services.ContentTypeService + .GetAllMediaTypes() + .Where(x => x.Alias.EndsWith("Folder")) + .Select(x => x.Id) + .ToArray(); + + if (folderTypes.Length == 0) + { + return new PagedResult>(0, pageNumber, pageSize); + } + + long total; + var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); + + return new PagedResult>(total, pageNumber, pageSize) + { + Items = children.Select(Mapper.Map>) + }; + } + + /// + /// Returns the root media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetRootMedia() + { + //TODO: Add permissions check! + + return Services.MediaService.GetRootMedia() + .Select(Mapper.Map>); + } + + #region GetChildren + + private int[] _userStartNodes; + protected int[] UserStartNodes + { + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + } + + /// + /// Returns the child media objects - using the entity INT id + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(int id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "", + Guid? dataTypeId = null) + { + //if a request is made for the root node data but the user's start node is not the default, then + // we need to return their start nodes + if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + { + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + if (ignoreUserStartNodes == false) + { + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map>) + }; + return pr; + } + } + + // else proceed as usual + + long totalChildren; + IMedia[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.MediaService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether user and group start node permissions should be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var entity = Services.EntityService.GetByKey(id); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether the user and group start node permissions will be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + var entity = Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = id.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + [EnsureUserPermissionForMedia("id")] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundMedia == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundMedia.IsInRecycleBin() == false) + { + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("move.Id")] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + var destinationParentID = move.ParentId; + var sourceParentID = toMove.ParentId; + + var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); + + if (sourceParentID == destinationParentID) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); + } + if (moveResult == false) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + else + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [MediaPostValidate] + [OutgoingEditorModelEvent] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(MediaItemBinder))] + MediaItemSave contentItem) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + && (contentItem.Action == ContentSaveAction.SaveNew)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw validation response + var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + //return the updated model + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + } + + break; + } + + return display; + } + + /// + /// Maps the property values to the persisted entity + /// + /// + protected override void MapPropertyValues(ContentBaseItemSave contentItem) + { + UpdateName(contentItem); + + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } + + /// + /// Empties the recycle bin + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage EmptyRecycleBin() + { + Services.MediaService.EmptyRecycleBin(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("sorted.ParentId")] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var mediaService = base.ApplicationContext.Services.MediaService; + var sortedMedia = new List(); + try + { + sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); + + // Save Media with new sort order and update content xml in db accordingly + if (mediaService.Sort(sortedMedia) == false) + { + LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update media sort order", ex); + throw; + } + } + + public MediaItemDisplay PostAddFolder(PostedFolder folder) + { + var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); + + var mediaService = ApplicationContext.Services.MediaService; + + var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(f, Security.CurrentUser.Id); + + return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); + } + + /// + /// Used to submit a media file + /// + /// + /// + /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. + /// + [FileUploadCleanupFilter(false)] + public async Task PostAddFile() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //get the string json from the request + string currentFolderId = result.FormData["currentFolder"]; + int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); + + var tempFiles = new PostedFiles(); + var mediaService = ApplicationContext.Services.MediaService; + + //in case we pass a path with a folder in it, we will create it and upload media to it. + if (result.FormData.ContainsKey("path")) + { + + var folders = result.FormData["path"].Split('/'); + + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; + + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); + + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); + + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) + { + var mediaType = Constants.Conventions.MediaTypes.File; + + if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) + { + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + { + mediaType = Constants.Conventions.MediaTypes.Image; + } + } + else + { + mediaType = result.FormData["contentTypeAlias"]; + } + + var mediaItemName = fileName.ToFriendlyName(); + + var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); + + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + f.SetValue(Constants.Conventions.Media.File, fileName, fs); + } + + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } + } + else + { + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("media/disallowedFileType"), + SpeechBubbleIcon.Warning)); + } + } + + //Different response if this is a 'blueimp' request + if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) + { + var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); + if (origin.Value == "blueimp") + { + return Request.CreateResponse(HttpStatusCode.OK, + tempFiles, + //Don't output the angular xsrf stuff, blue imp doesn't like that + new JsonMediaTypeFormatter()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); + } + + /// + /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT + /// + /// + /// + /// If true, this will check if the current user has access to the resolved integer parent id + /// and if that check fails an unauthorized exception will occur + /// + /// + private int GetParentIdAsInt(string parentId, bool validatePermissions) + { + int intParentId; + GuidUdi parentUdi; + + // test for udi + if (GuidUdi.TryParse(parentId, out parentUdi)) + { + parentId = parentUdi.Guid.ToString(); + } + + //if it's not an INT then we'll check for GUID + if (int.TryParse(parentId, out intParentId) == false) + { + // if a guid then try to look up the entity + Guid idGuid; + if (Guid.TryParse(parentId, out idGuid)) + { + var entity = Services.EntityService.GetByKey(idGuid); + if (entity != null) + { + intParentId = entity.Id; + } + else + { + throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); + } + } + else + { + throw new HttpResponseException( + Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); + } + } + + //ensure the user has access to this folder by parent id! + if (validatePermissions && CheckPermissions( + new Dictionary(), + Security.CurrentUser, + Services.MediaService, + Services.EntityService, + intParentId) == false) + { + throw new HttpResponseException(Request.CreateResponse( + HttpStatusCode.Forbidden, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning)))); + } + + return intParentId; + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IMedia ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var mediaService = Services.MediaService; + var toMove = mediaService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + else + { + var parent = mediaService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + + return toMove; + } + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// Specifies the already resolved content item to check against, setting this ignores the nodeId + /// + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, IEntityService entityService, int nodeId, IMedia media = null) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (mediaService == null) throw new ArgumentNullException("mediaService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + media = mediaService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IMedia).ToString()] = media; + } + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? user.HasMediaRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinMedia) + ? user.HasMediaBinAccess(entityService) + : user.HasPathAccess(media, entityService); + + return hasPathAccess; + } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index 4ed46de135..f2174ab120 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,46 +1,51 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents a content property to be saved - /// - [DataContract(Name = "property", Namespace = "")] - public class ContentPropertyBasic - { - /// - /// This is the cmsPropertyData ID - /// - /// - /// This is not really used for anything - /// - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "value")] - public object Value { get; set; } - - [DataMember(Name = "alias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Alias { get; set; } - - [DataMember(Name = "editor", IsRequired = false)] - public string Editor { get; set; } - - /// - /// Flags the property to denote that it can contain sensitive data - /// - [DataMember(Name = "isSensitive", IsRequired = false)] - public bool IsSensitive { get; set; } - - /// - /// Used internally during model mapping - /// - [IgnoreDataMember] - internal PropertyEditor PropertyEditor { get; set; } - - } -} +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a content property to be saved + /// + [DataContract(Name = "property", Namespace = "")] + public class ContentPropertyBasic + { + /// + /// This is the cmsPropertyData ID + /// + /// + /// This is not really used for anything + /// + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + + [DataMember(Name = "dataTypeId", IsRequired = true)] + [Required] + public Guid DataTypeId { get; set; } + + [DataMember(Name = "value")] + public object Value { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Alias { get; set; } + + [DataMember(Name = "editor", IsRequired = false)] + public string Editor { get; set; } + + /// + /// Flags the property to denote that it can contain sensitive data + /// + [DataMember(Name = "isSensitive", IsRequired = false)] + public bool IsSensitive { get; set; } + + /// + /// Used internally during model mapping + /// + [IgnoreDataMember] + internal PropertyEditor PropertyEditor { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index ccf22a8c34..02cf570f7d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -1,75 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a base generic ContentPropertyBasic from a Property - /// - /// - internal class ContentPropertyBasicConverter : ITypeConverter - where T : ContentPropertyBasic, new() - { - protected IDataTypeService DataTypeService { get; private set; } - - private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; - - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) - { - DataTypeService = dataTypeService; - } - - /// - /// Assigns the PropertyEditor, Id, Alias and Value to the property - /// - /// - public virtual T Convert(ResolutionContext context) - { - var property = context.SourceValue as Property; - if (property == null) - throw new InvalidOperationException("Source value is not a property."); - - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - if (editor == null) - { - LogHelper.Error>( - "No property editor found, converting to a Label", - new NullReferenceException("The property editor with alias " + - property.PropertyType.PropertyEditorAlias + " does not exist")); - - editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); - } - - var result = new T - { - Id = property.Id, - Alias = property.Alias, - PropertyEditor = editor, - Editor = editor.Alias - }; - - // if there's a set of property aliases specified, we will check if the current property's value should be mapped. - // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - if (context.Options.Items.ContainsKey("IncludeProperties")) - { - var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; - if (includeProperties != null && includeProperties.Contains(property.Alias) == false) - { - return result; - } - } - - // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); - return result; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a base generic ContentPropertyBasic from a Property + /// + /// + internal class ContentPropertyBasicConverter : ITypeConverter + where T : ContentPropertyBasic, new() + { + protected IDataTypeService DataTypeService { get; private set; } + + private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; + + public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + { + DataTypeService = dataTypeService; + } + + /// + /// Assigns the PropertyEditor, Id, Alias and Value to the property + /// + /// + public virtual T Convert(ResolutionContext context) + { + var property = context.SourceValue as Property; + if (property == null) + throw new InvalidOperationException("Source value is not a property."); + + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + if (editor == null) + { + LogHelper.Error>( + "No property editor found, converting to a Label", + new NullReferenceException("The property editor with alias " + + property.PropertyType.PropertyEditorAlias + " does not exist")); + + editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); + } + + var dataTypeDefinition = DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); + + var result = new T + { + Id = property.Id, + DataTypeId = dataTypeDefinition.Key, + Alias = property.Alias, + PropertyEditor = editor, + Editor = editor.Alias + }; + + // if there's a set of property aliases specified, we will check if the current property's value should be mapped. + // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. + if (context.Options.Items.ContainsKey("IncludeProperties")) + { + var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; + if (includeProperties != null && includeProperties.Contains(property.Alias) == false) + { + return result; + } + } + + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. + result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); + return result; + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 2ea41ff128..f2f83d1133 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -1,401 +1,407 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Web.Search; - -namespace Umbraco.Web.Trees -{ - /// - /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from - /// TreeController. - /// - [AngularJsonOnlyConfiguration] - public abstract class TreeControllerBase : UmbracoAuthorizedApiController - { - protected TreeControllerBase() - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - - /// - /// The method called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// Returns the menu structure for the node - /// - /// - /// - /// - protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// The name to display on the root node - /// - public abstract string RootNodeDisplayName { get; } - - /// - /// Gets the current tree alias from the attribute assigned to it. - /// - public abstract string TreeAlias { get; } - - /// - /// Returns the root node for the tree - /// - /// - /// - public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var node = CreateRootNode(queryStrings); - - //add the tree alias to the root - node.AdditionalData["treeAlias"] = TreeAlias; - - AddQueryStringsToAdditionalData(node, queryStrings); - - //check if the tree is searchable and add that to the meta data as well - if (this is ISearchableTree) - { - node.AdditionalData.Add("searchable", "true"); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) - { - node.RoutePath = "#"; - } - - OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); - - return node; - } - - /// - /// The action called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// JSON markup for jsTree - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var nodes = GetTreeNodes(id, queryStrings); - - foreach (var node in nodes) - { - AddQueryStringsToAdditionalData(node, queryStrings); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog((queryStrings))) - { - foreach (var node in nodes) - { - node.RoutePath = "#"; - } - } - - //raise the event - OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); - - return nodes; - } - - /// - /// The action called to render the menu for a tree node - /// - /// - /// - /// - public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var menu = GetMenuForNode(id, queryStrings); - //raise the event - OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); - return menu; - } - - /// - /// Helper method to create a root model for a tree - /// - /// - protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); - - var node = new TreeNode( - rootNodeAsString, - null, //this is a root node, there is no parent - Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), - Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) - { - HasChildren = true, - RoutePath = currApp, - Name = RootNodeDisplayName - }; - - return node; - } - - #region Create TreeNode methods - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) - { - Name = title, - Icon = icon, - NodeType = TreeAlias - }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; - return node; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - treeNode.Udi = udi; - return treeNode; - } - - #endregion - - /// - /// The AdditionalData of a node is always populated with the query string data, this method performs this - /// operation and ensures that special values are not inserted or that duplicate keys are not added. - /// - /// - /// - protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) - { - foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) - { - node.AdditionalData.Add(q.Key, q.Value); - } - } - - /// - /// If the request is for a dialog mode tree - /// - /// - /// - protected bool IsDialog(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); - } - - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); - } - - /// - /// An event that allows developers to modify the tree node collection that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler TreeNodesRendering; - - private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) - { - var handler = TreeNodesRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developer to modify the root tree node that is being rendered - /// - public static event TypedEventHandler RootNodeRendering; - - private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) - { - var handler = RootNodeRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developers to modify the meun that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler MenuRendering; - - private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) - { - var handler = MenuRendering; - if (handler != null) handler(instance, e); - } - } -} +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using System.Web.Http.ModelBinding; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Search; + +namespace Umbraco.Web.Trees +{ + /// + /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from + /// TreeController. + /// + [AngularJsonOnlyConfiguration] + public abstract class TreeControllerBase : UmbracoAuthorizedApiController + { + protected TreeControllerBase() + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) + { + } + + /// + /// The method called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// Returns the menu structure for the node + /// + /// + /// + /// + protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// The name to display on the root node + /// + public abstract string RootNodeDisplayName { get; } + + /// + /// Gets the current tree alias from the attribute assigned to it. + /// + public abstract string TreeAlias { get; } + + /// + /// Returns the root node for the tree + /// + /// + /// + public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var node = CreateRootNode(queryStrings); + + //add the tree alias to the root + node.AdditionalData["treeAlias"] = TreeAlias; + + AddQueryStringsToAdditionalData(node, queryStrings); + + //check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + { + node.AdditionalData.Add("searchable", "true"); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + node.RoutePath = "#"; + } + + OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); + + return node; + } + + /// + /// The action called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// JSON markup for jsTree + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var nodes = GetTreeNodes(id, queryStrings); + + foreach (var node in nodes) + { + AddQueryStringsToAdditionalData(node, queryStrings); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog((queryStrings))) + { + foreach (var node in nodes) + { + node.RoutePath = "#"; + } + } + + //raise the event + OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); + + return nodes; + } + + /// + /// The action called to render the menu for a tree node + /// + /// + /// + /// + public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var menu = GetMenuForNode(id, queryStrings); + //raise the event + OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); + return menu; + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); + + var node = new TreeNode( + rootNodeAsString, + null, //this is a root node, there is no parent + Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), + Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) + { + HasChildren = true, + RoutePath = currApp, + Name = RootNodeDisplayName + }; + + return node; + } + + #region Create TreeNode methods + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) + { + Name = title, + Icon = icon, + NodeType = TreeAlias + }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; + return node; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + treeNode.Udi = udi; + return treeNode; + } + + #endregion + + /// + /// The AdditionalData of a node is always populated with the query string data, this method performs this + /// operation and ensures that special values are not inserted or that duplicate keys are not added. + /// + /// + /// + protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) + { + foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) + { + node.AdditionalData.Add(q.Key, q.Value); + } + } + + /// + /// If the request is for a dialog mode tree + /// + /// + /// + protected bool IsDialog(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); + } + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } + + /// + /// An event that allows developers to modify the tree node collection that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler TreeNodesRendering; + + private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) + { + var handler = TreeNodesRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developer to modify the root tree node that is being rendered + /// + public static event TypedEventHandler RootNodeRendering; + + private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) + { + var handler = RootNodeRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developers to modify the meun that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler MenuRendering; + + private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) + { + var handler = MenuRendering; + if (handler != null) handler(instance, e); + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index c79b9f8781..20e02d1c42 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Web.Trees -{ - /// - /// Common query string parameters used for tree query strings - /// - internal struct TreeQueryStringParameters - { - public const string IsDialog = "isDialog"; - public const string Application = "application"; - public const string StartNodeId = "startNodeId"; - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; - //public const string OnNodeClick = "OnNodeClick"; - //public const string RenderParent = "RenderParent"; - } -} +namespace Umbraco.Web.Trees +{ + /// + /// Common query string parameters used for tree query strings + /// + internal struct TreeQueryStringParameters + { + public const string IsDialog = "isDialog"; + public const string Application = "application"; + public const string StartNodeId = "startNodeId"; + public const string DataTypeId = "dataTypeId"; + //public const string OnNodeClick = "OnNodeClick"; + //public const string RenderParent = "RenderParent"; + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 64fe9a4b65..57a7db4889 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,148 +1,154 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic.Actions; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Auth filter to check if the current user has access to the content item (by id). - /// - /// - /// - /// This first checks if the user can access this based on their start node, and then checks node permissions - /// - /// By default the permission that is checked is browse but this can be specified in the ctor. - /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. - /// - public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute - { - private readonly int? _nodeId; - private readonly string _paramName; - private readonly char? _permissionToCheck; - - /// - /// This constructor will only be able to test the start node access - /// - public EnsureUserPermissionForContentAttribute(int nodeId) - { - _nodeId = nodeId; - } - - public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) - : this(nodeId) - { - _permissionToCheck = permissionToCheck; - } - - public EnsureUserPermissionForContentAttribute(string paramName) - { - if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); - - _paramName = paramName; - _permissionToCheck = ActionBrowse.Instance.Letter; - } - - public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) - : this(paramName) - { - _permissionToCheck = permissionToCheck; - } - - public override bool AllowMultiple - { - get { return true; } - } - - public override void OnActionExecuting(HttpActionContext actionContext) - { - if (UmbracoContext.Current.Security.CurrentUser == null) - { - //not logged in - throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); - } - - var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && - bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); - - int nodeId; - if (_nodeId.HasValue == false) - { - var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); - - if (actionContext.ActionArguments[parts[0]] == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - - if (parts.Length == 1) - { - var argument = actionContext.ActionArguments[parts[0]].ToString(); - // if the argument is an int, it will parse and can be assigned to nodeId - // if might be a udi, so check that next - // otherwise treat it as a guid - unlikely we ever get here - if (int.TryParse(argument, out int parsedId)) - { - nodeId = parsedId; - } - else if (Udi.TryParse(argument, true, out Udi udi)) - { - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; - } - else - { - Guid.TryParse(argument, out Guid key); - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; - } - } - else - { - //now we need to see if we can get the property of whatever object it is - var pType = actionContext.ActionArguments[parts[0]].GetType(); - var prop = pType.GetProperty(parts[1]); - if (prop == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); - } - } - else - { - nodeId = _nodeId.Value; - } - - if (ContentController.CheckPermissions( - actionContext.Request.Properties, - UmbracoContext.Current.Security.CurrentUser, - ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, - _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, - ignoreUserStartNodes: ignoreUserStartNodes)) - { - base.OnActionExecuting(actionContext); - } - else - { - throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); - } - - } - - - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; +using umbraco.BusinessLogic.Actions; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Auth filter to check if the current user has access to the content item (by id). + /// + /// + /// + /// This first checks if the user can access this based on their start node, and then checks node permissions + /// + /// By default the permission that is checked is browse but this can be specified in the ctor. + /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. + /// + public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute + { + private readonly int? _nodeId; + private readonly string _paramName; + private readonly char? _permissionToCheck; + + /// + /// This constructor will only be able to test the start node access + /// + public EnsureUserPermissionForContentAttribute(int nodeId) + { + _nodeId = nodeId; + } + + public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) + : this(nodeId) + { + _permissionToCheck = permissionToCheck; + } + + public EnsureUserPermissionForContentAttribute(string paramName) + { + if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); + + _paramName = paramName; + _permissionToCheck = ActionBrowse.Instance.Letter; + } + + public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) + : this(paramName) + { + _permissionToCheck = permissionToCheck; + } + + public override bool AllowMultiple + { + get { return true; } + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + if (UmbracoContext.Current.Security.CurrentUser == null) + { + //not logged in + throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); + } + + var ignoreUserStartNodes = false; + + if (actionContext.ActionArguments.ContainsKey("dataTypeId") && + Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + + int nodeId; + if (_nodeId.HasValue == false) + { + var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + + if (actionContext.ActionArguments[parts[0]] == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + + if (parts.Length == 1) + { + var argument = actionContext.ActionArguments[parts[0]].ToString(); + // if the argument is an int, it will parse and can be assigned to nodeId + // if might be a udi, so check that next + // otherwise treat it as a guid - unlikely we ever get here + if (int.TryParse(argument, out int parsedId)) + { + nodeId = parsedId; + } + else if (Udi.TryParse(argument, true, out Udi udi)) + { + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; + } + else + { + Guid.TryParse(argument, out Guid key); + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; + } + } + else + { + //now we need to see if we can get the property of whatever object it is + var pType = actionContext.ActionArguments[parts[0]].GetType(); + var prop = pType.GetProperty(parts[1]); + if (prop == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); + } + } + else + { + nodeId = _nodeId.Value; + } + + if (ContentController.CheckPermissions( + actionContext.Request.Properties, + UmbracoContext.Current.Security.CurrentUser, + ApplicationContext.Current.Services.UserService, + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) + { + base.OnActionExecuting(actionContext); + } + else + { + throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); + } + + } + + + + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 23d4fb871c..40f0ad2a79 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -8,13 +8,12 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { /// - /// This inspects the result of the action that returns a collection of content and removes + /// This inspects the result of the action that returns a collection of content and removes /// any item that the current user doesn't have access to /// internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute @@ -79,12 +78,18 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + Guid? dataTypeId = Guid.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.DataTypeId), out var temp) ? (Guid?)temp : null; + + if (dataTypeId.HasValue == false) return; + + var ignoreUserStartNodes = ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); if (ignoreUserStartNodes == false) { FilterBasedOnStartNode(items, user); } + } internal void FilterBasedOnStartNode(IList items, IUser user) From b55d2a8a41c96f9e6461157f45c7f8df27d64733 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 09:55:46 +0200 Subject: [PATCH 04/35] Modelsbuilder is asking for too old a version of the CodeAnalysis dependency --- build/NuSpecs/UmbracoCms.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 8b650edc13..7d864fb03a 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,6 +22,7 @@ + From ad4416f6633c95520d9dff8f009d2f43f5093b26 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:07:45 +0200 Subject: [PATCH 05/35] So apparently Microsoft.CodeAnalysis.CSharp version 1.3.2 contains the dll that has the version 1.3.1.. very confusing --- build/NuSpecs/UmbracoCms.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 7d864fb03a..838ec5eff0 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,7 @@ - + From 5ebdfc7b1ce7e9329af64e51f1004a481a8fefb4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:22:16 +0200 Subject: [PATCH 06/35] Previously added a few too many binding redirects to the web.config --- src/Umbraco.Web.UI/web.Template.config | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 583952f29d..4ff70db974 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,22 +438,6 @@ - - - - - - - - - - - - - - - - From 2898203cb9d21acfb3430f5b04a50e991de414b8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 10:35:05 +0200 Subject: [PATCH 07/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 Bugfixes for the "Bypass security" feature + filter out possible sensitive data --- .../src/common/resources/content.resource.js | 6 +++- .../src/common/resources/entity.resource.js | 15 +++++--- .../contentpicker/contentpicker.controller.js | 2 +- .../ContentEditing/ContentPropertyBasic.cs | 5 ++- ...EnsureUserPermissionForContentAttribute.cs | 17 +++++++--- .../FilterAllowedOutgoingMediaAttribute.cs | 34 +++++++++++++++++++ 6 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 31eccfb5aa..d85cc74d7d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -331,12 +331,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { //now copy back to the options we will use options = defaults; + var args = [{ id: id }]; + if(options.dataTypeId){ + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { dataTypeId: options.dataTypeId }])), + args)), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index fff4ddde37..3991932d2d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -304,16 +304,21 @@ function entityResource($q, $http, umbRequestHelper) { //now copy back to the options we will use options = defaults; + + var args = [ + { id: id }, + { type: type }, + ]; + if(options.dataTypeId){ + args.push({dataTypeId: options.dataTypeId}); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [ - { id: id }, - { type: type }, - { dataTypeId: options.dataTypeId } - ])), + args)), 'Failed to retrieve ancestor data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 627baa3e7a..187b86a811 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -133,7 +133,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper idType: "int" }; - dialogOptions.dataTypeId = $scope.model.dataTypeId; //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); @@ -184,6 +183,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.contentPickerOverlay = dialogOptions; $scope.contentPickerOverlay.view = "treepicker"; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index f2174ab120..9edbab04ae 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -21,9 +21,8 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } - [DataMember(Name = "dataTypeId", IsRequired = true)] - [Required] - public Guid DataTypeId { get; set; } + [DataMember(Name = "dataTypeId", IsRequired = false)] + public Guid? DataTypeId { get; set; } [DataMember(Name = "value")] public object Value { get; set; } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 57a7db4889..dfdea268c4 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -75,11 +75,20 @@ namespace Umbraco.Web.WebApi.Filters var ignoreUserStartNodes = false; - if (actionContext.ActionArguments.ContainsKey("dataTypeId") && - Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + if (actionContext.ActionArguments.ContainsKey("dataTypeId")) { - ignoreUserStartNodes = - ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (actionContext.ActionArguments.TryGetValue("dataTypeId", out var dataTypeIdValue)) + { + var dataTypeIdString = dataTypeIdValue?.ToString(); + if (string.IsNullOrEmpty(dataTypeIdString) == false && + Guid.TryParse(dataTypeIdString, out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService + .IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + } + } int nodeId; diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 40f0ad2a79..d054337fd9 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -89,9 +89,43 @@ namespace Umbraco.Web.WebApi.Filters { FilterBasedOnStartNode(items, user); } + else + { + FilterOutPossibleSensitiveData(items); + } } + /// + /// Removes all properties of the items except the umbracoFile. + /// + /// + private void FilterOutPossibleSensitiveData(IList items) + { + foreach (dynamic item in items) + { + + if (item.Properties != null) + { + if (item.Properties is IList properties) + // Iterate reverse, because we removed from the same list, such that the ordering of indexes not + // changes doing the iteration + for (var i = properties.Count - 1; i >= 0; i--) + { + dynamic property = properties[i]; + if (property.Alias != null && property.Alias is string) + { + var alias = property.Alias as string; + if (string.Equals(alias, Constants.Conventions.Media.File) == false) + { + properties.RemoveAt(i); + } + } + } + } + } + } + internal void FilterBasedOnStartNode(IList items, IUser user) { var toRemove = new List(); From 40d4a8fe6aa40a46a1b186b93f1e2dafcfcb84c8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 13:32:53 +0200 Subject: [PATCH 08/35] Modelsbuilder is asking for too old a version of CodeAnalysis, Immutable and Metadata dependencies --- build/NuSpecs/UmbracoCms.nuspec | 1 - src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 20 ++++++++++---------- src/Umbraco.Web.UI/packages.config | 10 +++++----- src/Umbraco.Web.UI/web.Template.config | 8 -------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 838ec5eff0..8b650edc13 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9d92d55388..a0624a2dd7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -155,11 +155,11 @@ ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - - ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll @@ -204,8 +204,8 @@ System - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll @@ -236,8 +236,8 @@ False False - - ..\packages\System.Reflection.Metadata.1.6.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll @@ -1002,8 +1002,8 @@ - - + + 11.0 diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index abcaf6bf83..18cbfdb042 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -19,9 +19,9 @@ - - - + + + @@ -36,7 +36,7 @@ - - + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 4ff70db974..0d26b71a38 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,14 +438,6 @@ - - - - - - - - From c4c2082fa93aa663349551009e8b52a04f9e54e0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 13:57:17 +0200 Subject: [PATCH 09/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Only allow "ignoreUserStartNodes" for custom data types and not the default build in. --- src/Umbraco.Core/Constants-DataTypes.cs | 295 ++++++++ src/Umbraco.Core/Models/DataTypeDefinition.cs | 477 ++++++------ .../Models/IDataTypeDefinition.cs | 53 +- .../Factories/DataTypeDefinitionFactory.cs | 196 ++--- .../Migrations/Initial/BaseDataCreation.cs | 684 +++++++++--------- src/Umbraco.Core/Services/DataTypeService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Models/Mapping/PreValueDisplayResolver.cs | 173 ++--- .../ContentPicker2PropertyEditor.cs | 10 +- .../PropertyEditors/GridPropertyEditor.cs | 16 +- .../MediaPicker2PropertyEditor.cs | 4 +- .../MultiNodeTreePicker2PropertyEditor.cs | 12 +- .../MultiUrlPickerPropertyEditor.cs | 2 +- .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 147 ++-- 15 files changed, 1217 insertions(+), 863 deletions(-) create mode 100644 src/Umbraco.Core/Constants-DataTypes.cs diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs new file mode 100644 index 0000000000..d5d0e99dab --- /dev/null +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -0,0 +1,295 @@ +using System; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + public static class DataTypes + { + + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + + /// + /// Guid for Content Picker as string + /// + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; + + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + + + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + + + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; + + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + + + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; + + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + + + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + + + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new Guid(Member); + + + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + + + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new Guid(Tags); + + + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + + + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + + + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); + + + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + + + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + + + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + + + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new Guid(Radiobox); + + + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new Guid(DatePicker); + + + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new Guid(Dropdown); + + + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + + + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new Guid(Checkbox); + + + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new Guid(Numeric); + + + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + + + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new Guid(Textstring); + + + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new Guid(Textarea); + + + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new Guid(Upload); + + + /// + /// Guid for Label as string + /// + public const string Label = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + + /// + /// Guid for Label + /// + public static readonly Guid LabelGuid = new Guid(Label); + + + } + } +} diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 3ba5125a90..5e1fd649ae 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -1,219 +1,260 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - /// - /// Definition of a DataType/PropertyEditor - /// - /// - /// The definition exists as a database reference between an actual DataType/PropertyEditor - /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. - /// - [Serializable] - [DataContract(IsReference = true)] - public class DataTypeDefinition : Entity, IDataTypeDefinition - { - private int _parentId; - private string _name; - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; - private string _propertyEditorAlias; - private DataTypeDatabaseType _databaseType; - - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] - [EditorBrowsable(EditorBrowsableState.Never)] - public DataTypeDefinition(int parentId, Guid controlId) - { - _parentId = parentId; - - _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); - if (_propertyEditorAlias == null) - { - //convert to Label! - LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); - _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; - } - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(int parentId, string propertyEditorAlias) - { - _parentId = parentId; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(string propertyEditorAlias) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); - } - - /// - /// Gets or sets the Id of the Parent entity - /// - /// Might not be necessary if handled as a relation? - [DataMember] - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the name of the current entity - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets or sets the path - /// - [DataMember] - public string Path //Setting this value should be handled by the class not the user - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - /// - /// Id of the user who created this entity - /// - [DataMember] - public int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } - } - - //NOTE: SD: Why do we have this ?? - - /// - /// Boolean indicating whether this entity is Trashed or not. - /// - [DataMember] - public bool Trashed - { - get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["Trashed"] = value; - } - } - - [DataMember] - public string PropertyEditorAlias - { - get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - /// - /// Id of the DataType control - /// - [DataMember] - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] - public Guid ControlId - { - get - { - return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( - _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; - } - set - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); - PropertyEditorAlias = alias; - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["ControlId"] = value; - } - } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - [DataMember] - public DataTypeDatabaseType DatabaseType - { - get { return _databaseType; } - set - { - SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - } +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + /// + /// Definition of a DataType/PropertyEditor + /// + /// + /// The definition exists as a database reference between an actual DataType/PropertyEditor + /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. + /// + [Serializable] + [DataContract(IsReference = true)] + public class DataTypeDefinition : Entity, IDataTypeDefinition + { + private int _parentId; + private string _name; + private int _sortOrder; + private int _level; + private string _path; + private int _creatorId; + private bool _trashed; + private string _propertyEditorAlias; + private DataTypeDatabaseType _databaseType; + + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] + [EditorBrowsable(EditorBrowsableState.Never)] + public DataTypeDefinition(int parentId, Guid controlId) + { + _parentId = parentId; + + _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); + if (_propertyEditorAlias == null) + { + //convert to Label! + LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); + _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; + } + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(int parentId, string propertyEditorAlias) + { + _parentId = parentId; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias, Guid uniqueId) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + IsBuildInDataType = IdsOfBuildInDataTypes.Contains(uniqueId); + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + } + + /// + /// Gets or sets the Id of the Parent entity + /// + /// Might not be necessary if handled as a relation? + [DataMember] + public int ParentId + { + get { return _parentId; } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + } + + /// + /// Gets or sets the name of the current entity + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets the sort order of the content entity + /// + [DataMember] + public int SortOrder + { + get { return _sortOrder; } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + } + + /// + /// Gets or sets the level of the content entity + /// + [DataMember] + public int Level + { + get { return _level; } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } + } + + /// + /// Gets or sets the path + /// + [DataMember] + public string Path //Setting this value should be handled by the class not the user + { + get { return _path; } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } + } + + /// + /// Id of the user who created this entity + /// + [DataMember] + public int CreatorId + { + get { return _creatorId; } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } + } + + //NOTE: SD: Why do we have this ?? + + /// + /// Boolean indicating whether this entity is Trashed or not. + /// + [DataMember] + public bool Trashed + { + get { return _trashed; } + internal set + { + SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["Trashed"] = value; + } + } + + [DataMember] + public string PropertyEditorAlias + { + get { return _propertyEditorAlias; } + set + { + SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + /// + /// Id of the DataType control + /// + [DataMember] + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] + public Guid ControlId + { + get + { + return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( + _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; + } + set + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); + PropertyEditorAlias = alias; + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["ControlId"] = value; + } + } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + [DataMember] + public DataTypeDatabaseType DatabaseType + { + get { return _databaseType; } + set + { + SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + + public bool IsBuildInDataType { get;} + } + } diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index a33736e604..f57a1bdca7 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -1,24 +1,29 @@ -using System; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - public interface IDataTypeDefinition : IUmbracoEntity - { - /// - /// The Property editor alias assigned to the data type - /// - string PropertyEditorAlias { get; set; } - - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] - Guid ControlId { get; set; } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - DataTypeDatabaseType DatabaseType { get; set; } - } -} \ No newline at end of file +using System; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + public interface IDataTypeDefinition : IUmbracoEntity + { + /// + /// The Property editor alias assigned to the data type + /// + string PropertyEditorAlias { get; set; } + + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] + Guid ControlId { get; set; } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + DataTypeDatabaseType DatabaseType { get; set; } + + /// + /// Gets information about whether this data type is build in or not. + /// + bool IsBuildInDataType { get;} + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index cf487fa1a2..14c162d8ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -1,98 +1,98 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DataTypeDefinitionFactory - { - private readonly Guid _nodeObjectTypeId; - private int _primaryKey; - - public DataTypeDefinitionFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public IDataTypeDefinition BuildEntity(DataTypeDto dto) - { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); - - - try - { - dataTypeDefinition.DisableChangeTracking(); - - dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); - dataTypeDefinition.Id = dto.DataTypeId; - dataTypeDefinition.Key = dto.NodeDto.UniqueId; - dataTypeDefinition.Level = dto.NodeDto.Level; - dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.Name = dto.NodeDto.Text; - dataTypeDefinition.ParentId = dto.NodeDto.ParentId; - dataTypeDefinition.Path = dto.NodeDto.Path; - dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; - dataTypeDefinition.Trashed = dto.NodeDto.Trashed; - dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; - } - finally - { - dataTypeDefinition.EnableChangeTracking(); - } - } - - public DataTypeDto BuildDto(IDataTypeDefinition entity) - { - var dataTypeDto = new DataTypeDto - { - PropertyEditorAlias = entity.PropertyEditorAlias, - DataTypeId = entity.Id, - DbType = entity.DatabaseType.ToString(), - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - dataTypeDto.PrimaryKey = _primaryKey; - } - - return dataTypeDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private NodeDto BuildNodeDto(IDataTypeDefinition entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } -} \ No newline at end of file +using System; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class DataTypeDefinitionFactory + { + private readonly Guid _nodeObjectTypeId; + private int _primaryKey; + + public DataTypeDefinitionFactory(Guid nodeObjectTypeId) + { + _nodeObjectTypeId = nodeObjectTypeId; + } + + #region Implementation of IEntityFactory + + public IDataTypeDefinition BuildEntity(DataTypeDto dto) + { + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias,dto.NodeDto.UniqueId); + + + try + { + dataTypeDefinition.DisableChangeTracking(); + + dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); + dataTypeDefinition.Id = dto.DataTypeId; + dataTypeDefinition.Key = dto.NodeDto.UniqueId; + dataTypeDefinition.Level = dto.NodeDto.Level; + dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.Name = dto.NodeDto.Text; + dataTypeDefinition.ParentId = dto.NodeDto.ParentId; + dataTypeDefinition.Path = dto.NodeDto.Path; + dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; + dataTypeDefinition.Trashed = dto.NodeDto.Trashed; + dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + dataTypeDefinition.ResetDirtyProperties(false); + return dataTypeDefinition; + } + finally + { + dataTypeDefinition.EnableChangeTracking(); + } + } + + public DataTypeDto BuildDto(IDataTypeDefinition entity) + { + var dataTypeDto = new DataTypeDto + { + PropertyEditorAlias = entity.PropertyEditorAlias, + DataTypeId = entity.Id, + DbType = entity.DatabaseType.ToString(), + NodeDto = BuildNodeDto(entity) + }; + + if (_primaryKey > 0) + { + dataTypeDto.PrimaryKey = _primaryKey; + } + + return dataTypeDto; + } + + #endregion + + public void SetPrimaryKey(int primaryKey) + { + _primaryKey = primaryKey; + } + + private NodeDto BuildNodeDto(IDataTypeDefinition entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + + return nodeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index b8e605e63e..e1716947e1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,342 +1,342 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - /// - /// Represents the initial data creation by running Insert for the base data. - /// - internal class BaseDataCreation - { - private readonly Database _database; - private readonly ILogger _logger; - - public BaseDataCreation(Database database, ILogger logger) - { - _database = database; - _logger = logger; - } - - /// - /// Initialize the base data creation by inserting the data foundation for umbraco - /// specific to a table - /// - /// Name of the table to create base data for - public void InitializeBaseData(string tableName) - { - _logger.Info(string.Format("Creating data in table {0}", tableName)); - - if(tableName.Equals("umbracoNode")) - { - CreateUmbracoNodeData(); - } - - if (tableName.Equals("umbracoLock")) - { - CreateUmbracoLockData(); - } - - if (tableName.Equals("cmsContentType")) - { - CreateCmsContentTypeData(); - } - - if (tableName.Equals("umbracoUser")) - { - CreateUmbracoUserData(); - } - - if (tableName.Equals("umbracoUserGroup")) - { - CreateUmbracoUserGroupData(); - } - - if (tableName.Equals("umbracoUser2UserGroup")) - { - CreateUmbracoUser2UserGroupData(); - } - - if (tableName.Equals("umbracoUserGroup2App")) - { - CreateUmbracoUserGroup2AppData(); - } - - if (tableName.Equals("cmsPropertyTypeGroup")) - { - CreateCmsPropertyTypeGroupData(); - } - - if (tableName.Equals("cmsPropertyType")) - { - CreateCmsPropertyTypeData(); - } - - if (tableName.Equals("umbracoLanguage")) - { - CreateUmbracoLanguageData(); - } - - if (tableName.Equals("cmsContentTypeAllowedContentType")) - { - CreateCmsContentTypeAllowedContentTypeData(); - } - - if(tableName.Equals("cmsDataType")) - { - CreateCmsDataTypeData(); - } - - if (tableName.Equals("cmsDataTypePreValues")) - { - CreateCmsDataTypePreValuesData(); - } - - if (tableName.Equals("umbracoRelationType")) - { - CreateUmbracoRelationTypeData(); - } - - if (tableName.Equals("cmsTaskType")) - { - CreateCmsTaskTypeData(); - } - - if (tableName.Equals("umbracoMigration")) - { - CreateUmbracoMigrationData(); - } - - _logger.Info(string.Format("Done creating data in table {0}", tableName)); - } - - private void CreateUmbracoNodeData() - { - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); - - //New UDI pickers with newer Ids - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - } - - private void CreateUmbracoLockData() - { - // all lock objects - _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); - } - - private void CreateCmsContentTypeData() - { - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); - } - - private void CreateUmbracoUserData() - { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - } - - private void CreateUmbracoUserGroupData() - { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); - } - - private void CreateUmbracoUser2UserGroupData() - { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins - _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data - } - - private void CreateUmbracoUserGroup2AppData() - { - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); - } - - private void CreateCmsPropertyTypeGroupData() - { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); - //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); - } - - private void CreateCmsPropertyTypeData() - { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - - } - - private void CreateUmbracoLanguageData() - { - _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); - } - - private void CreateCmsContentTypeAllowedContentTypeData() - { - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); - } - - private void CreateCmsDataTypeData() - { - //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - - //New UDI pickers with newer Ids - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); - } - - private void CreateCmsDataTypePreValuesData() - { - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); - - //defaults for the member list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); - - //layouts for the list view - var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; - var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; - - //defaults for the media list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); - - //default's for MultipleMediaPickerAlias picker - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); - - // Defaults for single item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); - - // Defaults for multiple item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); - } - - private void CreateUmbracoRelationTypeData() - { - var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - } - - private void CreateCmsTaskTypeData() - { - _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); - } - - private void CreateUmbracoMigrationData() - { - var dto = new MigrationDto - { - Id = 1, - Name = Constants.System.UmbracoMigrationName, - Version = UmbracoVersion.GetSemanticVersion().ToString(), - CreateDate = DateTime.Now - }; - - _database.Insert("umbracoMigration", "pk", false, dto); - } - } -} +using System; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Initial +{ + /// + /// Represents the initial data creation by running Insert for the base data. + /// + internal class BaseDataCreation + { + private readonly Database _database; + private readonly ILogger _logger; + + public BaseDataCreation(Database database, ILogger logger) + { + _database = database; + _logger = logger; + } + + /// + /// Initialize the base data creation by inserting the data foundation for umbraco + /// specific to a table + /// + /// Name of the table to create base data for + public void InitializeBaseData(string tableName) + { + _logger.Info(string.Format("Creating data in table {0}", tableName)); + + if(tableName.Equals("umbracoNode")) + { + CreateUmbracoNodeData(); + } + + if (tableName.Equals("umbracoLock")) + { + CreateUmbracoLockData(); + } + + if (tableName.Equals("cmsContentType")) + { + CreateCmsContentTypeData(); + } + + if (tableName.Equals("umbracoUser")) + { + CreateUmbracoUserData(); + } + + if (tableName.Equals("umbracoUserGroup")) + { + CreateUmbracoUserGroupData(); + } + + if (tableName.Equals("umbracoUser2UserGroup")) + { + CreateUmbracoUser2UserGroupData(); + } + + if (tableName.Equals("umbracoUserGroup2App")) + { + CreateUmbracoUserGroup2AppData(); + } + + if (tableName.Equals("cmsPropertyTypeGroup")) + { + CreateCmsPropertyTypeGroupData(); + } + + if (tableName.Equals("cmsPropertyType")) + { + CreateCmsPropertyTypeData(); + } + + if (tableName.Equals("umbracoLanguage")) + { + CreateUmbracoLanguageData(); + } + + if (tableName.Equals("cmsContentTypeAllowedContentType")) + { + CreateCmsContentTypeAllowedContentTypeData(); + } + + if(tableName.Equals("cmsDataType")) + { + CreateCmsDataTypeData(); + } + + if (tableName.Equals("cmsDataTypePreValues")) + { + CreateCmsDataTypePreValuesData(); + } + + if (tableName.Equals("umbracoRelationType")) + { + CreateUmbracoRelationTypeData(); + } + + if (tableName.Equals("cmsTaskType")) + { + CreateCmsTaskTypeData(); + } + + if (tableName.Equals("umbracoMigration")) + { + CreateUmbracoMigrationData(); + } + + _logger.Info(string.Format("Done creating data in table {0}", tableName)); + } + + private void CreateUmbracoNodeData() + { + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = Constants.DataTypes.LabelGuid, Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = Constants.DataTypes.UploadGuid, Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = Constants.DataTypes.TextareaGuid, Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = Constants.DataTypes.TextstringGuid, Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = Constants.DataTypes.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.NumericGuid, Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxGuid, Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownGuid, Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerGuid, Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Constants.DataTypes.RadioboxGuid, Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = Constants.DataTypes.TagsGuid, Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = Constants.DataTypes.ImageCropperGuid, Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = Constants.DataTypes.MemberGuid, Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); + + //New UDI pickers with newer Ids + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = Constants.DataTypes.ContentPickerGuid, Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = Constants.DataTypes.MemberPickerGuid, Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = Constants.DataTypes.MediaPickerGuid, Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = Constants.DataTypes.MultipleMediaPickerGuid, Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = Constants.DataTypes.RelatedLinksGuid, Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + } + + private void CreateUmbracoLockData() + { + // all lock objects + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); + } + + private void CreateCmsContentTypeData() + { + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); + } + + private void CreateUmbracoUserData() + { + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + } + + private void CreateUmbracoUserGroupData() + { + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + } + + private void CreateUmbracoUser2UserGroupData() + { + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins + _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data + } + + private void CreateUmbracoUserGroup2AppData() + { + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); + } + + private void CreateCmsPropertyTypeGroupData() + { + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); + //membership property group + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); + } + + private void CreateCmsPropertyTypeData() + { + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + //membership property types + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); + + } + + private void CreateUmbracoLanguageData() + { + _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); + } + + private void CreateCmsContentTypeAllowedContentTypeData() + { + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); + } + + private void CreateCmsDataTypeData() + { + //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + + //New UDI pickers with newer Ids + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); + } + + private void CreateCmsDataTypePreValuesData() + { + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); + + //defaults for the member list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + + //layouts for the list view + var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; + var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; + + //defaults for the media list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + + //default's for MultipleMediaPickerAlias picker + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); + + // Defaults for single item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); + + // Defaults for multiple item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); + } + + private void CreateUmbracoRelationTypeData() + { + var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + } + + private void CreateCmsTaskTypeData() + { + _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); + } + + private void CreateUmbracoMigrationData() + { + var dto = new MigrationDto + { + Id = 1, + Name = Constants.System.UmbracoMigrationName, + Version = UmbracoVersion.GetSemanticVersion().ToString(), + CreateDate = DateTime.Now + }; + + _database.Insert("umbracoMigration", "pk", false, dto); + } + } +} diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 43c2b69c47..b02fb644d8 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -356,7 +356,7 @@ namespace Umbraco.Core.Services if (dataType != null) { var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + if (preValues.PreValuesAsDictionary.TryGetValue(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) { return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0b3c923c83..f29c1bea46 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -324,6 +324,9 @@ + + Constants.cs + diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 9d1331e691..652bd34fff 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -1,84 +1,93 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class PreValueDisplayResolver : ValueResolver> - { - private readonly IDataTypeService _dataTypeService; - - public PreValueDisplayResolver(IDataTypeService dataTypeService) - { - _dataTypeService = dataTypeService; +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class PreValueDisplayResolver : ValueResolver> + { + private readonly IDataTypeService _dataTypeService; + + public PreValueDisplayResolver(IDataTypeService dataTypeService) + { + _dataTypeService = dataTypeService; } - /// - /// Maps pre-values in the dictionary to the values for the fields. - /// - /// The fields. - /// The pre-values. - /// The editor alias. - internal static void MapPreValueValuesToPreValueFields(PreValueFieldDisplay[] fields, IDictionary preValues, string editorAlias) - { - if (fields == null) throw new ArgumentNullException(nameof(fields)); - if (preValues == null) throw new ArgumentNullException(nameof(preValues)); - - // Now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields) - { - // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue - var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); - if (preValue.Key == null) - { - LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); - continue; - } - - field.Value = preValue.Value; - } - } - - internal IEnumerable Convert(IDataTypeDefinition source) - { - PropertyEditor propEd = null; - if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); - if (propEd == null) - { - throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); - } - } - - // Set up the defaults - var dataTypeService = _dataTypeService; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); - IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); - var result = Enumerable.Empty().ToArray(); - - // If we have a prop editor, then format the pre-values based on it and create it's fields - if (propEd != null) - { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); - dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); - } - - MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); - - return result; - } - - protected override IEnumerable ResolveCore(IDataTypeDefinition source) - { - return Convert(source); - } - } -} + /// + /// Maps pre-values in the dictionary to the values for the fields. + /// + /// The fields. + /// The pre-values. + /// The editor alias. + internal static void MapPreValueValuesToPreValueFields(IEnumerable fields, IDictionary preValues, string editorAlias) + { + if (fields == null) throw new ArgumentNullException(nameof(fields)); + if (preValues == null) throw new ArgumentNullException(nameof(preValues)); + + // Now we need to wire up the pre-values values with the actual fields defined + foreach (var field in fields) + { + // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue + var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); + if (preValue.Key == null) + { + LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); + continue; + } + + field.Value = preValue.Value; + } + } + + internal IEnumerable Convert(IDataTypeDefinition source) + { + PropertyEditor propEd = null; + if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) + { + propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); + if (propEd == null) + { + throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); + } + } + + // Set up the defaults + var dataTypeService = _dataTypeService; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); + IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); + var result = Enumerable.Empty(); + + // If we have a prop editor, then format the pre-values based on it and create it's fields + if (propEd != null) + { + result = propEd.PreValueEditor.Fields.Select(Mapper.Map).AsEnumerable(); + if (source.IsBuildInDataType) + { + result = RemovePreValuesNotSupportedOnBuildInTypes(result); + } + dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); + } + + MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); + + return result; + } + + private IEnumerable RemovePreValuesNotSupportedOnBuildInTypes(IEnumerable preValues) + { + return preValues.Where(preValue => string.Equals(preValue.Key, Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes) == false); + } + + protected override IEnumerable ResolveCore(IDataTypeDefinition source) + { + return Convert(source); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 0039385e5f..46f251ebf4 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,11 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -40,9 +40,9 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() - { + { Key = "showOpenButton", View = "boolean", Name = "Show open button (this feature is in preview!)", @@ -50,7 +50,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 4a8803a099..94aed3e852 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] public class GridPropertyEditor : PropertyEditor, IApplicationEventHandler - { + { private static void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) { @@ -49,7 +49,7 @@ namespace Umbraco.Web.PropertyEditors foreach (var areaVal in areaVals) { - //TODO: If it's not a string, then it's a json formatted value - + //TODO: If it's not a string, then it's a json formatted value - // we cannot really index this in a smart way since it could be 'anything' if (areaVal.Type == JTokenType.String) { @@ -87,12 +87,12 @@ namespace Umbraco.Web.PropertyEditors catch (InvalidCastException) { //swallow...on purpose, there's a chance that this isn't the json format we are looking for - // and we don't want that to affect the website. + // and we don't want that to affect the website. } catch (JsonException) { - //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect - // the website. + //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect + // the website. } catch (ArgumentException) { @@ -137,7 +137,7 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } @@ -152,7 +152,7 @@ namespace Umbraco.Web.PropertyEditors { /// /// We're going to bind to the Examine events so we can ensure grid data is index nicely. - /// + /// protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { foreach (var i in ExamineManager.Instance.IndexProviderCollection.OfType()) @@ -175,7 +175,7 @@ namespace Umbraco.Web.PropertyEditors public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); + _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); } #endregion } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 6aa7ab4a54..e62ac98abd 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors } internal IDictionary InternalPreValues; - + public override IDictionary DefaultPreValues { get { return InternalPreValues; } @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f57a9951b6..b79cd96313 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,11 +16,11 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } - + protected override PreValueEditor CreatePreValueEditor() { return new MultiNodePickerPreValueEditor(); @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors //create the fields Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." @@ -81,7 +81,7 @@ namespace Umbraco.Web.PropertyEditors Name = "Show open button (this feature is in preview!)", Description = "Opens the node in a dialog" }); - } + } /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set @@ -117,9 +117,9 @@ namespace Umbraco.Web.PropertyEditors { result["multiPicker"] = "1"; } - } + } } - + return result; } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index f3ae317efa..ea3a1572b1 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors { Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index a96c0724ff..289e6588a9 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -33,11 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index 69445bc304..9fd2bf6de4 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -1,73 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - //need to figure out how to use this... - internal class RichTextPreValueEditor : PreValueEditor - { - public RichTextPreValueEditor() - { - //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), - // see below for examples. - - //use a custom editor too - Fields.Add(new PreValueField() - { - View = "views/propertyeditors/rte/rte.prevalues.html", - HideLabel = true, - Key = "editor" - }); - - Fields.Add(new PreValueField() - { - Key = "ignoreUserStartNodes", - View = "boolean", - Name = "Ignore user start nodes", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." - }); - - Fields.Add(new PreValueField() - { - Name = "Hide Label", - View = "boolean", - Key = "hideLabel" - }); - } - - //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: - //#1 - the property name becomes the Key: - // - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#2 - You can specify a custom Key: - // - // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, - // this is why in this case I find it easier to use the ctor logic but thats just an opinion - // - Any value specified for this property attribute will override the values set in on the class instance of the field, this - // allows you to re-use class instances of fields if you want. - // - // [PreValueField(typeof(EditorPreValueField))] - // public string Editor { get; set; } - - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public class EditorPreValueField : PreValueField - // { - // public EditorPreValueField() - // { - // //add any required server validators for this field - // Validators.Add(new RegexValidator("^\\d*$")); - // //You could also set the field properties directly here if you wanted instead of the attribute - // } - // } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + //need to figure out how to use this... + internal class RichTextPreValueEditor : PreValueEditor + { + public RichTextPreValueEditor() + { + //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), + // see below for examples. + + //use a custom editor too + Fields.Add(new PreValueField() + { + View = "views/propertyeditors/rte/rte.prevalues.html", + HideLabel = true, + Key = "editor" + }); + + Fields.Add(new PreValueField() + { + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + + Fields.Add(new PreValueField() + { + Name = "Hide Label", + View = "boolean", + Key = "hideLabel" + }); + } + + //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: + //#1 - the property name becomes the Key: + // + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#2 - You can specify a custom Key: + // + // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, + // this is why in this case I find it easier to use the ctor logic but thats just an opinion + // - Any value specified for this property attribute will override the values set in on the class instance of the field, this + // allows you to re-use class instances of fields if you want. + // + // [PreValueField(typeof(EditorPreValueField))] + // public string Editor { get; set; } + + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public class EditorPreValueField : PreValueField + // { + // public EditorPreValueField() + // { + // //add any required server validators for this field + // Validators.Add(new RegexValidator("^\\d*$")); + // //You could also set the field properties directly here if you wanted instead of the attribute + // } + // } + + } +} From 8d12170def955ee00f53dcc7d19d441ad532a395 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 14:58:58 +0200 Subject: [PATCH 10/35] Correct NuGet transform for the web.config file in the Views folder --- build/NuSpecs/tools/Views.Web.config.install.xdt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 4d660301a8..828bb8612f 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -8,7 +8,7 @@ - + @@ -18,13 +18,13 @@ - + From eab597a2a8bc09916c912eeceba1d46cbd359b99 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 08:58:25 +0200 Subject: [PATCH 11/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - fix for js linter --- .../src/common/resources/entity.resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 3991932d2d..31ff7424ac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -307,7 +307,7 @@ function entityResource($q, $http, umbRequestHelper) { var args = [ { id: id }, - { type: type }, + { type: type } ]; if(options.dataTypeId){ args.push({dataTypeId: options.dataTypeId}); From d4311fe4ab50b3a5bd7716765739f5deb7826d74 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 10:52:35 +0200 Subject: [PATCH 12/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Replaced client side method to get Auchors with a server side version, and eliminated the use of contentResource in linkPikers --- src/Umbraco.Core/Services/ContentService.cs | 5843 +++++++++-------- src/Umbraco.Core/Services/IContentService.cs | 1437 ++-- .../src/common/resources/entity.resource.js | 35 + .../src/common/services/tinymce.service.js | 1729 +++-- .../common/dialogs/linkpicker.controller.js | 18 +- .../linkpicker/linkpicker.controller.js | 16 +- .../grid/editors/rte.controller.js | 29 +- .../propertyeditors/rte/rte.controller.js | 34 +- src/Umbraco.Web/Editors/EntityController.cs | 20 + .../Models/ContentEditing/UrlAndAnchors.cs | 21 + src/Umbraco.Web/Umbraco.Web.csproj | 4053 ++++++------ 11 files changed, 6665 insertions(+), 6570 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index a1e7c9955e..cf49e892dd 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1,2904 +1,2939 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the Content Service, which is an easy access to operations involving - /// - public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations - { - private readonly IPublishingStrategy2 _publishingStrategy; - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private readonly IUserService _userService; - - //Support recursive locks because some of the methods that require locking call other methods that require locking. - //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - - public ContentService( - IDatabaseUnitOfWorkProvider provider, - RepositoryFactory repositoryFactory, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IDataTypeService dataTypeService, - IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); - _dataTypeService = dataTypeService; - _userService = userService; - } - - #region Static Queries - - private IQuery _notTrashedQuery; - - #endregion - - public int CountPublished(string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.CountPublished(contentTypeAlias); - } - } - - public int Count(string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.CountDescendants(parentId, contentTypeAlias); - } - } - - /// - /// Used to bulk update the permissions set for a content item. This will replace all permissions - /// assigned to an entity with a list of user id & permission pairs. - /// - /// - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.ReplaceContentPermissions(permissionSet); - uow.Commit(); - } - } - - /// - /// Assigns a single permission to the current content item for the specified group ids - /// - /// - /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.AssignEntityPermission(entity, permission, groupIds); - uow.Commit(); - } - } - - /// - /// Returns implicit/inherited permissions assigned to the content item for all user groups - /// - /// - /// - public EntityPermissionCollection GetPermissionsForEntity(IContent content) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetPermissionsForEntity(content.Id); - } - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0) - { - var parent = GetById(parentId); - return CreateContent(name, parent, contentTypeAlias, userId); - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); - var parent = GetById(content.ParentId); - content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - uow.Commit(); - } - - return content; - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parent, contentType); - content.Path = string.Concat(parent.Path, ",", content.Id); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - uow.Commit(); - } - - return content; - } - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); - - using (var uow = UowProvider.GetUnitOfWork()) - { - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var saveEventArgs = new SaveEventArgs(content); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - content.CreatorId = userId; - content.WriterId = userId; - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - uow.Commit(); - } - - return content; - } - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parent, contentType); - - using (var uow = UowProvider.GetUnitOfWork()) - { - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var saveEventArgs = new SaveEventArgs(content); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - content.CreatorId = userId; - content.WriterId = userId; - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - uow.Commit(); - } - - return content; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IContent GetById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets objects by Ids - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - var idsArray = ids.ToArray(); - if (idsArray.Length == 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // ensure that the result has the order based on the ids passed in - var result = repository.GetAll(idsArray); - var content = result.ToDictionary(x => x.Id, x => x); - - var sortedResult = idsArray.Select(x => - { - IContent c; - return content.TryGetValue(x, out c) ? c : null; - }).WhereNotNull(); - - return sortedResult; - } - } - - /// - /// Gets objects by Ids - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - var idsArray = ids.ToArray(); - if (idsArray.Length == 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // ensure that the result has the order based on the ids passed in - var result = repository.GetAll(idsArray); - var content = result.ToDictionary(x => x.Key, x => x); - - var sortedResult = idsArray.Select(x => - { - IContent c; - return content.TryGetValue(x, out c) ? c : null; - }).WhereNotNull(); - - return sortedResult; - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - public IContent GetById(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Get(key); - } - } - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetContentOfContentType(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ContentTypeId == id); - return repository.GetByQuery(query); - } - } - - internal IEnumerable GetPublishedContentOfContentType(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ContentTypeId == id); - return repository.GetByPublishedVersion(query); - } - } - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - public IEnumerable GetByLevel(int level) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IContent GetByVersion(Guid versionId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetByVersion(versionId); - } - } - - - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetAllVersions(id); - } - } - - /// - /// Gets a list of all version Ids for the given content item ordered so latest is first - /// - /// - /// The maximum number of rows to return - /// - public IEnumerable GetVersionIds(int id, int maxRows) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetVersionIds(id, maxRows); - } - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - var content = GetById(id); - return GetAncestors(content); - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IContent content) - { - //null check otherwise we get exceptions - if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - - var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); - if (ids.Any() == false) - return new List(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == id); - return repository.GetByQuery(query).OrderBy(x => x.SortOrder); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - long total; - var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); - totalChildren = Convert.ToInt32(total); - return result; - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder; - // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method) - query.Where(x => x.ParentId == id); - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - long total; - var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); - totalChildren = Convert.ToInt32(total); - return result; - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // get query - if the id is System Root, then just get all - var query = Query.Builder; - if (id != Constants.System.Root) - { - var entityRepository = RepositoryFactory.CreateEntityRepository(uow); - var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); - if (contentPath.Length == 0) - { - totalChildren = 0; - return Enumerable.Empty(); - } - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); - } - - - // get filter - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // get query - if the id is System Root, then just get all - var query = Query.Builder; - if (id != Constants.System.Root) - { - var entityRepository = RepositoryFactory.CreateEntityRepository(uow); - var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); - if (contentPath.Length == 0) - { - totalChildren = 0; - return Enumerable.Empty(); - } - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - } - } - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - public IEnumerable GetChildrenByName(int parentId, string name) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(int id) - { - var content = GetById(id); - return content == null ? Enumerable.Empty() : GetDescendants(content); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(IContent content) - { - //This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption - if (content.ValidatePath() == false) - throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId)); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var pathMatch = content.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - return repository.GetByQuery(query); - } - } - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IContent GetParent(int id) - { - var content = GetById(id); - return GetParent(content); - } - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - public IContent GetParent(IContent content) - { - if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) - return null; - - return GetById(content.ParentId); - } - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - public IContent GetPublishedVersion(int id) - { - var version = GetVersions(id); - return version.FirstOrDefault(x => x.Published); - } - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - public IContent GetPublishedVersion(IContent content) - { - if (content.Published) return content; - return content.HasPublishedVersion - ? GetByVersion(content.PublishedVersionGuid) - : null; - } - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootContent() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all published content items - /// - /// - internal IEnumerable GetAllPublished() - { - //create it once if it is needed (no need for locking here) - if (_notTrashedQuery == null) - { - _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); - } - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetByPublishedVersion(_notTrashedQuery); - } - } - - /// - /// Gets a collection of objects, which has an expiration date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForExpiration() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); - return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); - } - } - - /// - /// Gets a collection of objects, which has a release date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForRelease() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetContentInRecycleBin() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); - return repository.GetByQuery(query); - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - public bool HasChildren(int id) - { - return CountChildren(id) > 0; - } - - internal int CountChildren(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == id); - return repository.Count(query); - } - } - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - public bool HasPublishedVersion(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - return repository.Count(query) > 0; - } - } - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// True if the Content can be published, otherwise False - public bool IsPublishable(IContent content) - { - int[] ids; - if (content.HasIdentity) - { - // get ids from path (we have identity) - // skip the first one that has to be -1 - and we don't care - // skip the last one that has to be "this" - and it's ok to stop at the parent - ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray(); - } - else - { - // no path yet (no identity), have to move up to parent - // skip the first one that has to be -1 - and we don't care - // don't skip the last one that is "parent" - var parent = GetById(content.ParentId); - if (parent == null) return false; - ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray(); - } - if (ids.Length == 0) - return false; - - // if the first one is recycle bin, fail fast - if (ids[0] == Constants.System.RecycleBinContent) - return false; - - // fixme - move to repository? - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql(@" - SELECT id - FROM umbracoNode - JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0 - WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)", - true, false, ids); - var x = uow.Database.Fetch(sql); - return ids.Length == x.Count; - } - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// This is not used for anything - /// True if publishing succeeded, otherwise False - /// - /// This is used for when a document type alias or a document type property is changed, the xml will need to - /// be regenerated. - /// - public bool RePublishAll(int userId = 0) - { - try - { - RebuildXmlStructures(); - return true; - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - return false; - } - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// - /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure - /// for all published content. - /// - internal void RePublishAll(params int[] contentTypeIds) - { - try - { - RebuildXmlStructures(contentTypeIds); - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - } - } - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public bool Publish(IContent content, int userId = 0) - { - var result = SaveAndPublishDo(content, userId); - Logger.Info("Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome"); - return result.Success; - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// - /// The list of statuses for all published items - IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished) - { - return PublishWithChildrenDo(content, userId, includeUnpublished); - } - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents) - { - return SaveAndPublishDo(content, userId, raiseEvents); - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) - { - return MoveToRecycleBinDo(content, userId, false); - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - /// - /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method - /// because it has already looked up all descendant nodes that will need to be recycled - /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations - /// - private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants) - { - var evtMsgs = EventMessagesFactory.Get(); - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - //Hack: this ensures that the entity's path is valid and if not it fixes/persists it - //see: http://issues.umbraco.org/issue/U4-9336 - content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); - var originalPath = content.Path; - var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - var moveInfo = new List> - { - moveEventInfo - }; - - //get descendents to process of the content item that is being moved to trash - must be done before changing the state below - //must be processed with shallowest levels first - var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level); - - //Do the updates for this item - var repository = RepositoryFactory.CreateContentRepository(uow); - //Make sure that published content is unpublished before being moved to the Recycle Bin - if (HasPublishedVersion(content.Id)) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(content, userId); - } - content.WriterId = userId; - content.ChangeTrashedState(true); - repository.AddOrUpdate(content); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! - UnPublish(descendant, userId); - descendant.WriterId = userId; - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - } - - moveEventArgs.CanCancel = false; - moveEventArgs.MoveInfoCollection = moveInfo; - uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed"); - - Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - uow.Commit(); - } - - return OperationStatus.Success(evtMsgs); - } - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - Attempt IContentServiceOperations.UnPublish(IContent content, int userId) - { - return UnPublishDo(content, false, userId); - } - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - public Attempt PublishWithStatus(IContent content, int userId = 0) - { - return ((IContentServiceOperations)this).Publish(content, userId); - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] - public bool PublishWithChildren(IContent content, int userId = 0) - { - var result = PublishWithChildrenDo(content, userId, true); - - //This used to just return false only when the parent content failed, otherwise would always return true so we'll - // do the same thing for the moment - if (result.All(x => x.Result.ContentItem.Id != content.Id)) - return false; - - return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// set to true if you want to also publish children that are currently unpublished - /// True if publishing succeeded, otherwise False - public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) - { - return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - public bool UnPublish(IContent content, int userId = 0) - { - var attempt = ((IContentServiceOperations)this).UnPublish(content, userId); - LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType)); - return attempt.Success; - } - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) - { - var result = SaveAndPublishDo(content, userId, raiseEvents); - return result.Success; - } - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) - { - return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); - } - - public IContent GetBlueprintById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - var blueprint = repository.Get(id); - if (blueprint != null) - ((Content) blueprint).IsBlueprint = true; - return blueprint; - } - } - - public IContent GetBlueprintById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - var blueprint = repository.Get(id); - if (blueprint != null) - ((Content)blueprint).IsBlueprint = true; - return blueprint; - } - } - - public void SaveBlueprint(IContent content, int userId = 0) - { - //always ensure the blueprint is at the root - if (content.ParentId != -1) - content.ParentId = -1; - - ((Content) content).IsBlueprint = true; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - if (string.IsNullOrWhiteSpace(content.Name)) - { - throw new ArgumentException("Cannot save content blueprint with empty name."); - } - - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - repository.AddOrUpdate(content); - - uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); - - uow.Commit(); - } - } - } - - public void DeleteBlueprint(IContent content, int userId = 0) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - repository.Delete(content); - uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint"); - uow.Commit(); - } - } - } - - public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) - { - if (blueprint == null) throw new ArgumentNullException("blueprint"); - - var contentType = blueprint.ContentType; - var content = new Content(name, -1, contentType); - content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); - - content.CreatorId = userId; - content.WriterId = userId; - - foreach (var property in blueprint.Properties) - content.SetValue(property.Alias, property.Value); - - return content; - } - - public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0) - { - using (new WriteLock(Locker)) - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - var contentTypeIdsA = contentTypeIds.ToArray(); - var query = new Query(); - if (contentTypeIdsA.Length > 0) - { - query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); - } - var blueprints = repository.GetByQuery(query).Select(x => - { - ((Content) x).IsBlueprint = true; - return x; - }).ToArray(); - - foreach (var blueprint in blueprints) - { - repository.Delete(blueprint); - } - - uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint"); - uow.Commit(); - } - } - - public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0) - { - DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId); - } - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IContent content, int userId = 0, bool raiseEvents = true) - { - ((IContentServiceOperations)this).Save(content, userId, raiseEvents); - } - - /// - /// Saves a collection of objects. - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents) - { - var asArray = contents.ToArray(); - - var evtMsgs = EventMessagesFactory.Get(); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(asArray, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - - // todo - understand what's a lock in a scope? - // (though, these locks are refactored in v8) - using (new WriteLock(Locker)) - { - var containsNew = asArray.Any(x => x.HasIdentity == false); - var repository = RepositoryFactory.CreateContentRepository(uow); - - if (containsNew) - { - foreach (var content in asArray) - { - content.WriterId = userId; - - //Only change the publish state if the "previous" version was actually published - if (content.Published) - content.ChangePublishedState(PublishedState.Saved); - - repository.AddOrUpdate(content); - //add or update preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - } - else - { - foreach (var content in asArray) - { - content.WriterId = userId; - repository.AddOrUpdate(content); - //add or update preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - } - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - uow.Commit(); - - return OperationStatus.Success(evtMsgs); - } - } - - /// - /// Permanently deletes an object. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - Attempt IContentServiceOperations.Delete(IContent content, int userId) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); - if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs, "Deleting")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - - //Make sure that published content is unpublished before being deleted - if (HasPublishedVersion(content.Id)) - { - UnPublish(content, userId); - } - - //Delete children before deleting the 'possible parent' - var children = GetChildren(content.Id); - foreach (var child in children) - { - Delete(child, userId); - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - repository.Delete(content); - - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(Deleted, this, deleteEventArgs, "Deleted"); - - Audit(uow, AuditType.Delete, "Delete Content performed by user", userId, content.Id); - uow.Commit(); - } - - return OperationStatus.Success(evtMsgs); - } - } - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt IContentServiceOperations.Publish(IContent content, int userId) - { - return SaveAndPublishDo(content, userId); - } - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents) - { - return Save(content, true, userId, raiseEvents); - } - - /// - /// Saves a collection of objects. - /// - /// - /// If the collection of content contains new objects that references eachother by Id or ParentId, - /// then use the overload Save method with a collection of Lazy . - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) - { - ((IContentServiceOperations)this).Save(contents, userId, raiseEvents); - } - - /// - /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// - /// Id of the - /// Optional Id of the user issueing the delete operation - public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) - { - using (new WriteLock(Locker)) - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - //track the 'root' items of the collection of nodes discovered to delete, we need to use - //these items to lookup descendants that are not of this doc type so they can be transfered - //to the recycle bin - IDictionary rootItems; - var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray(); - - if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting")) - { - uow.Commit(); - return; - } - - //Determine the items that will need to be recycled (that are children of these content items but not of these content types) - var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository); - - //move each item to the bin starting with the deepest items - foreach (var child in contentToRecycle.OrderByDescending(x => x.Level)) - { - MoveToRecycleBinDo(child, userId, true); - } - - foreach (var content in contentToDelete) - { - Delete(content, userId); - } - - Audit(uow, AuditType.Delete, - string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)), - userId, Constants.System.Root); - uow.Commit(); - } - } - - /// - /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional Id of the user issueing the delete operation - public void DeleteContentOfType(int contentTypeId, int userId = 0) - { - DeleteContentOfTypes(new[] {contentTypeId}, userId); - } - - /// - /// Permanently deletes an object as well as all of its Children. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - public void Delete(IContent content, int userId = 0) - { - ((IContentServiceOperations)this).Delete(content, userId); - } - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs, "DeletingVersions")) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.DeleteVersions(id, versionDate); - deleteRevisionsEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs, "DeletedVersions"); - - Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); - uow.Commit(); - } - } - - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions")) - { - uow.Commit(); - return; - } - - if (deletePriorVersions) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.DeleteVersion(versionId); - - uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions"); - - Audit(uow, AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); - uow.Commit(); - } - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - public void MoveToRecycleBin(IContent content, int userId = 0) - { - ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); - } - - /// - /// Moves an object to a new location by changing its parent id. - /// - /// - /// If the object is already published it will be - /// published after being moved to its new location. Otherwise it'll just - /// be saved with a new parent id. - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = 0) - { - using (new WriteLock(Locker)) - { - //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == Constants.System.RecycleBinContent) - { - MoveToRecycleBin(content, userId); - return; - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); - var moveEventArgs = new MoveEventArgs(moveEventInfo); - if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs, "Moving")) - { - uow.Commit(); - return; - } - - //used to track all the moved entities to be given to the event - var moveInfo = new List>(); - - //call private method that does the recursive moving - PerformMove(content, parentId, userId, moveInfo); - - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - uow.Events.Dispatch(Moved, this, moveEventArgs, "Moved"); - - Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id); - uow.Commit(); - } - } - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - public void EmptyRecycleBin() => EmptyRecycleBin(0); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - public void EmptyRecycleBin(int userId = 0) - { - using (new WriteLock(Locker)) - { - var nodeObjectType = Constants.ObjectTypes.DocumentGuid; - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - //Create a dictionary of ids -> dictionary of property aliases + values - var entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - var files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); - - var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, entities, files); - if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) - { - uow.Commit(); - return; - } - - var success = repository.EmptyRecycleBin(); - recycleBinEventArgs.CanCancel = false; - recycleBinEventArgs.RecycleBinEmptiedSuccessfully = success; - uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); - - Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", userId, Constants.System.RecycleBinContent); - uow.Commit(); - } - } - } - - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. Recursively copies all children. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) - { - return Copy(content, parentId, relateToOriginal, true, userId); - } - - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0) - { - //TODO: This all needs to be managed correctly so that the logic is submitted in one - // transaction, the CRUD needs to be moved to the repo - - using (new WriteLock(Locker)) - { - var copy = content.DeepCloneWithResetIdentities(); - copy.ParentId = parentId; - - // A copy should never be set to published automatically even if the original was. - copy.ChangePublishedState(PublishedState.Unpublished); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); - if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs)) - { - uow.Commit(); - return null; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; - - //get the current permissions, if there are any explicit ones they need to be copied - var currentPermissions = GetPermissionsForEntity(content); - currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); - - repository.AddOrUpdate(copy); - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - - //add permissions - if (currentPermissions.Count > 0) - { - var permissionSet = new ContentPermissionSet(copy, currentPermissions); - repository.AddOrUpdatePermissions(permissionSet); - } - - uow.Commit(); // todo - this should flush, not commit - - //Special case for the associated tags - //TODO: Move this to the repository layer in a single transaction! - //don't copy tags data in tags table if the item is in the recycle bin - if (parentId != Constants.System.RecycleBinContent) - { - var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); - foreach (var tag in tags) - uow.Database.Insert(new TagRelationshipDto - { - NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId - }); - } - uow.Commit(); // todo - this should flush, not commit - - if (recursive) - { - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) - { - //TODO: This shouldn't recurse back to this method, it should be done in a private method - // that doesn't have a nested lock and so we can perform the entire operation in one commit. - Copy(child, copy.Id, relateToOriginal, true, userId); - } - } - copyEventArgs.CanCancel = false; - uow.Events.Dispatch(Copied, this, copyEventArgs); - Audit(uow, AuditType.Copy, "Copy Content performed by user", userId, content.Id); - uow.Commit(); - } - - return copy; - } - } - - - /// - /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. - /// - /// The to send to publication - /// Optional Id of the User issueing the send to publication - /// True if sending publication was succesfull otherwise false - public bool SendToPublication(IContent content, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var sendToPublishEventArgs = new SendToPublishEventArgs(content); - if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) - { - uow.Commit(); - return false; - } - - //Save before raising event - Save(content, userId); - sendToPublishEventArgs.CanCancel = false; - uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); - - Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - uow.Commit(); - - return true; - } - } - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// - /// The way data is stored actually only allows us to rollback on properties - /// and not data like Name and Alias of the Content. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - public IContent Rollback(int id, Guid versionId, int userId = 0) - { - var content = GetByVersion(versionId); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var rollbackEventArgs = new RollbackEventArgs(content); - if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) - { - uow.Commit(); - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - content.WriterId = userId; - content.CreatorId = userId; - content.ChangePublishedState(PublishedState.Unpublished); - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - rollbackEventArgs.CanCancel = false; - uow.Events.Dispatch(RolledBack, this, rollbackEventArgs); - - Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); - uow.Commit(); - } - - return content; - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) - { - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var asArray = items.ToArray(); - var saveEventArgs = new SaveEventArgs(asArray); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return false; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - var i = 0; - foreach (var content in asArray) - { - //If the current sort order equals that of the content - //we don't need to update it, so just increment the sort order - //and continue. - if (content.SortOrder == i) - { - i++; - continue; - } - - content.SortOrder = i; - content.WriterId = userId; - i++; - - if (content.Published) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - var published = _publishingStrategy.Publish(uow, content, userId).Success; - shouldBePublished.Add(content); - } - else - shouldBeSaved.Add(content); - - repository.AddOrUpdate(content); - //add or update a preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - foreach (var content in shouldBePublished) - { - //Create and Save ContentXml DTO - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - if (shouldBePublished.Any()) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); - } - - Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); - uow.Commit(); - } - } - - return true; - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of node Ids passed in. - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - public bool Sort(int[] ids, int userId = 0, bool raiseEvents = true) - { - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - using (new WriteLock(Locker)) - { - var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); - if (allContent.Any() == false) - { - return false; - } - var items = ids.Select(x => allContent[x]).ToArray(); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(items); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return false; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - var i = 0; - foreach (var content in items) - { - //If the current sort order equals that of the content - //we don't need to update it, so just increment the sort order - //and continue. - if (content.SortOrder == i) - { - i++; - continue; - } - - content.SortOrder = i; - content.WriterId = userId; - i++; - - if (content.Published) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - var published = _publishingStrategy.Publish(uow, content, userId).Success; - shouldBePublished.Add(content); - } - else - shouldBeSaved.Add(content); - - repository.AddOrUpdate(content); - //add or update a preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - foreach (var content in shouldBePublished) - { - //Create and Save ContentXml DTO - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - if (shouldBePublished.Any()) - { - //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! - _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); - } - - Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); - uow.Commit(); - } - } - - return true; - } - - public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - var query = new Query(); - if (documentTypeIds.Length > 0) - { - query.Where(x => documentTypeIds.Contains(x.ContentTypeId)); - } - return repository.GetByQuery(query).Select(x => - { - ((Content) x).IsBlueprint = true; - return x; - }); - } - } - - /// - /// Gets paged content descendants as XML by path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of content items - public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, - //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and - // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order - new[] { "level", "parentID", "sortOrder" }, - out totalRecords); - return contents; - } - } - - /// - /// This builds the Xml document used for the XML cache - /// - /// - public XmlDocument BuildXmlCache() - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var result = repository.BuildXmlCache(); - uow.Commit(); - return result; - } - } - - public XmlDocument BuildPreviewXmlCache() - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var result = repository.BuildPreviewXmlCache(); - uow.Commit(); - return result; - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all documents - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all content - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - repository.RebuildXmlStructures( - content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - - Audit(uow, AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - uow.Commit(); - } - } - - #region Internal Methods - - /// - /// Gets a collection of descendants by the first Parent. - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - internal IEnumerable GetPublishedDescendants(IContent content) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - return repository.GetByPublishedVersion(query); - } - } - - #endregion - - #region Private Methods - - /// - /// Hack: This is used to fix some data if an entity's properties are invalid/corrupt - /// - /// - private void QuickUpdate(IContent content) - { - if (content == null) throw new ArgumentNullException("content"); - if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity"); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.AddOrUpdate(content); - uow.Commit(); - } - } - - private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) - { - var auditRepo = RepositoryFactory.CreateAuditRepository(uow); - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } - - //TODO: All of this needs to be moved to the repository - private void PerformMove(IContent content, int parentId, int userId, ICollection> moveInfo) - { - //add a tracking item to use in the Moved event - moveInfo.Add(new MoveEventInfo(content, content.Path, parentId)); - - content.WriterId = userId; - if (parentId == Constants.System.Root) - { - content.Path = string.Concat(Constants.System.Root, ",", content.Id); - content.Level = 1; - } - else - { - var parent = GetById(parentId); - content.Path = string.Concat(parent.Path, ",", content.Id); - content.Level = parent.Level + 1; - } - - //If Content is being moved away from Recycle Bin, its state should be un-trashed - if (content.Trashed && parentId != Constants.System.RecycleBinContent) - { - content.ChangeTrashedState(false, parentId); - } - else - { - content.ParentId = parentId; - } - - //If Content is published, it should be (re)published from its new location - if (content.Published) - { - //If Content is Publishable its saved and published - //otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed. - if (IsPublishable(content)) - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - SaveAndPublish(content, userId); - } - else - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - Save(content, false, userId); - - //TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to - // change how this method calls "Save" as it needs to save using an internal method - using (var uow = UowProvider.GetUnitOfWork()) - { - var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, content); - - var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToDataString() }; - var exists = - uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) != - null; - int result = exists - ? uow.Database.Update(poco) - : Convert.ToInt32(uow.Database.Insert(poco)); - uow.Commit(); - } - } - } - else - { - //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine - Save(content, userId); - } - - //Ensure that Path and Level is updated on children - var children = GetChildren(content.Id).ToArray(); - if (children.Any()) - { - foreach (var child in children) - { - PerformMove(child, content.Id, userId, moveInfo); - } - } - } - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published - /// - /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published - /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that - /// are to be published. - /// - private IEnumerable> PublishWithChildrenDo( - IContent content, int userId = 0, bool includeUnpublished = false) - { - if (content == null) throw new ArgumentNullException("content"); - - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - //Hack: this ensures that the entity's path is valid and if not it fixes/persists it - //see: http://issues.umbraco.org/issue/U4-9336 - content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); - - var result = new List>(); - - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", - content.Name, content.Id)); - result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); - return result; - } - - //Content contains invalid property values and can therefore not be published - fire event? - if (!content.IsValid()) - { - Logger.Info( - string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - result.Add( - Attempt.Fail( - new PublishStatus(content, PublishStatusType.FailedContentInvalid, evtMsgs) - { - InvalidProperties = ((ContentBase)content).LastInvalidProperties - })); - return result; - } - - //Consider creating a Path query instead of recursive method: - //var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); - - var updated = new List(); - var list = new List(); - list.Add(content); //include parent item - list.AddRange(GetDescendants(content)); - - var internalStrategy = _publishingStrategy; - - using (var uow = UowProvider.GetUnitOfWork()) - { - //Publish and then update the database with new status - var publishedOutcome = internalStrategy.PublishWithChildren(uow, list, userId, includeUnpublished).ToArray(); - var published = publishedOutcome - .Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished) - // ensure proper order (for events) - cannot publish a child before its parent! - .OrderBy(x => x.Result.ContentItem.Level) - .ThenBy(x => x.Result.ContentItem.SortOrder); - - var repository = RepositoryFactory.CreateContentRepository(uow); - - //NOTE The Publish with subpages-dialog was used more as a republish-type-thing, so we'll have to include PublishStatusType.SuccessAlreadyPublished - //in the updated-list, so the Published event is triggered with the expected set of pages and the xml is updated. - foreach (var item in published) - { - item.Result.ContentItem.WriterId = userId; - repository.AddOrUpdate(item.Result.ContentItem); - //add or update a preview - repository.AddOrUpdatePreviewXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - //add or update the published xml - repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - updated.Add(item.Result.ContentItem); - } - - //Save xml to db and call following method to fire event: - _publishingStrategy.PublishingFinalized(uow, updated, false); - - Audit(uow, AuditType.Publish, "Publish with Children performed by user", userId, content.Id); - uow.Commit(); - - return publishedOutcome; - } - } - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - private Attempt UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0) - { - var newest = GetById(content.Id); // ensure we have the newest version - if (content.Version != newest.Version) // but use the original object if it's already the newest version - content = newest; - - var evtMsgs = EventMessagesFactory.Get(); - - var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version - if (published == null) - { - return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - var unpublished = _publishingStrategy.UnPublish(uow, content, userId); - if (unpublished == false) - { - uow.Commit(); - return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - content.WriterId = userId; - repository.AddOrUpdate(content); - // is published is not newest, reset the published flag on published version - if (published.Version != content.Version) - repository.ClearPublished(published); - repository.DeleteContentXml(content); - - //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache - if (omitCacheRefresh == false) - _publishingStrategy.UnPublishingFinalized(uow, content); - - Audit(uow, AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); - uow.Commit(); - } - - return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.Success, evtMsgs)); - } - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - - //Has this content item previously been published? If so, we don't need to refresh the children - var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id - var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success - - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - publishStatus.StatusType = CheckAndLogIsPublishable(content); - //if it is not successful, then check if the props are valid - if ((int)publishStatus.StatusType < 10) - { - //Content contains invalid property values and can therefore not be published - fire event? - publishStatus.StatusType = CheckAndLogIsValid(content); - //set the invalid properties (if there are any) - publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties; - } - //if we're still successful, then publish using the strategy - if (publishStatus.StatusType == PublishStatusType.Success) - { - //Publish and then update the database with new status - var publishResult = _publishingStrategy.Publish(uow, content, userId); - //set the status type to the publish result - publishStatus.StatusType = publishResult.Result.StatusType; - } - - //we are successfully published if our publishStatus is still Successful - bool published = publishStatus.StatusType == PublishStatusType.Success; - - var repository = RepositoryFactory.CreateContentRepository(uow); - - if (published == false) - { - content.ChangePublishedState(PublishedState.Saved); - } - //Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - - if (published) - { - //Content Xml - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - //Save xml to db and call following method to fire event through PublishingStrategy to update cache - if (published) - { - _publishingStrategy.PublishingFinalized(uow, content); - } - - //We need to check if children and their publish state to ensure that we 'republish' content that was previously published - if (published && previouslyPublished == false && HasChildren(content.Id)) - { - //TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy - var descendants = GetPublishedDescendants(content); - _publishingStrategy.PublishingFinalized(uow, descendants, false); - } - - uow.Commit(); - - if (publishStatus.StatusType == PublishStatusType.Success) - { - Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id); - } - else - { - Audit(uow, AuditType.Save, "Save performed by user", userId, content.Id); - } - uow.Commit(); - - return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); - } - } - } - - /// - /// Saves a single object - /// - /// The to save - /// Boolean indicating whether or not to change the Published state upon saving - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - private Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - - if (string.IsNullOrWhiteSpace(content.Name)) - { - throw new ArgumentException("Cannot save content with empty name."); - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - //Only change the publish state if the "previous" version was actually published or marked as unpublished - if (changeState && (content.Published || ((Content)content).PublishedState == PublishedState.Unpublished)) - content.ChangePublishedState(PublishedState.Saved); - - repository.AddOrUpdate(content); - - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id); - uow.Commit(); - } - - return OperationStatus.Success(evtMsgs); - } - } - - private PublishStatusType CheckAndLogIsPublishable(IContent content) - { - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because its parent is not published.", - content.Name, content.Id)); - return PublishStatusType.FailedPathNotPublished; - } - - if (content.ExpireDate.HasValue && content.ExpireDate.Value > DateTime.MinValue && DateTime.Now > content.ExpireDate.Value) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' has expired and could not be published.", - content.Name, content.Id)); - return PublishStatusType.FailedHasExpired; - } - - if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' is awaiting release and could not be published.", - content.Name, content.Id)); - return PublishStatusType.FailedAwaitingRelease; - } - - return PublishStatusType.Success; - } - - private PublishStatusType CheckAndLogIsValid(IContent content) - { - //Content contains invalid property values and can therefore not be published - fire event? - if (content.IsValid() == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - return PublishStatusType.FailedContentInvalid; - } - - return PublishStatusType.Success; - } - - private IContentType FindContentTypeByAlias(string contentTypeAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentTypeRepository(uow); - - var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No ContentType matching the passed in Alias: '{0}' was found", - contentTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", - contentTypeAlias)); - return contentType; - } - } - - #endregion - - #region Proxy Event Handlers - /// - /// Occurs before publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Publishing - { - add { PublishingStrategy.Publishing += value; } - remove { PublishingStrategy.Publishing -= value; } - } - - /// - /// Occurs after publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Published - { - add { PublishingStrategy.Published += value; } - remove { PublishingStrategy.Published -= value; } - } - /// - /// Occurs before unpublish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> UnPublishing - { - add { PublishingStrategy.UnPublishing += value; } - remove { PublishingStrategy.UnPublishing -= value; } - } - - /// - /// Occurs after unpublish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> UnPublished - { - add { PublishingStrategy.UnPublished += value; } - remove { PublishingStrategy.UnPublished -= value; } - } - #endregion - - #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Create - /// - [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] - public static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Content object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs before Copy - /// - public static event TypedEventHandler> Copying; - - /// - /// Occurs after Copy - /// - public static event TypedEventHandler> Copied; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before Rollback - /// - public static event TypedEventHandler> RollingBack; - - /// - /// Occurs after Rollback - /// - public static event TypedEventHandler> RolledBack; - - /// - /// Occurs before Send to Publish - /// - public static event TypedEventHandler> SendingToPublish; - - /// - /// Occurs after Send to Publish - /// - public static event TypedEventHandler> SentToPublish; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - - /// - /// Occurs after a blueprint has been saved. - /// - public static event TypedEventHandler> SavedBlueprint; - - /// - /// Occurs after a blueprint has been deleted. - /// - public static event TypedEventHandler> DeletedBlueprint; - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the Content Service, which is an easy access to operations involving + /// + public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations + { + private readonly IPublishingStrategy2 _publishingStrategy; + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + + //Support recursive locks because some of the methods that require locking call other methods that require locking. + //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + public ContentService( + IDatabaseUnitOfWorkProvider provider, + RepositoryFactory repositoryFactory, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IDataTypeService dataTypeService, + IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); + _dataTypeService = dataTypeService; + _userService = userService; + } + + #region Static Queries + + private IQuery _notTrashedQuery; + + #endregion + + public int CountPublished(string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.CountPublished(contentTypeAlias); + } + } + + public int Count(string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + + /// + /// Used to bulk update the permissions set for a content item. This will replace all permissions + /// assigned to an entity with a list of user id & permission pairs. + /// + /// + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.ReplaceContentPermissions(permissionSet); + uow.Commit(); + } + } + + /// + /// Assigns a single permission to the current content item for the specified group ids + /// + /// + /// + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.AssignEntityPermission(entity, permission, groupIds); + uow.Commit(); + } + } + + /// + /// Returns implicit/inherited permissions assigned to the content item for all user groups + /// + /// + /// + public EntityPermissionCollection GetPermissionsForEntity(IContent content) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetPermissionsForEntity(content.Id); + } + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0) + { + var parent = GetById(parentId); + return CreateContent(name, parent, contentTypeAlias, userId); + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + var content = new Content(name, parentId, contentType); + var parent = GetById(content.ParentId); + content.Path = string.Concat(parent.IfNotNull(x => x.Path, content.ParentId.ToString()), ",", content.Id); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + content.CreatorId = userId; + content.WriterId = userId; + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + uow.Commit(); + } + + return content; + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var contentType = FindContentTypeByAlias(contentTypeAlias); + var content = new Content(name, parent, contentType); + content.Path = string.Concat(parent.Path, ",", content.Id); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + content.CreatorId = userId; + content.WriterId = userId; + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + uow.Commit(); + } + + return content; + } + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + var content = new Content(name, parentId, contentType); + + using (var uow = UowProvider.GetUnitOfWork()) + { + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var saveEventArgs = new SaveEventArgs(content); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + content.CreatorId = userId; + content.WriterId = userId; + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + uow.Commit(); + } + + return content; + } + + public IList GetAnchorValuesFromRTEs(int id) + { + var result = new List(); + + var content = GetById(id); + + foreach (var contentProperty in content.Properties) + { + if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) + { + var value = contentProperty.Value.ToString(); + + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } + } + + + return result; + } + + public IList GetAnchorValuesFromRTEContent(string rteContent) + { + var result = new List(); + var regex = new Regex(""); + var matches = regex.Matches(rteContent); + + foreach (Match match in matches) + { + result.Add(match.Value.Split('\"')[1]); + } + + return result; + } + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var contentType = FindContentTypeByAlias(contentTypeAlias); + var content = new Content(name, parent, contentType); + + using (var uow = UowProvider.GetUnitOfWork()) + { + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var saveEventArgs = new SaveEventArgs(content); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + content.CreatorId = userId; + content.WriterId = userId; + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + uow.Commit(); + } + + return content; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IContent GetById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets objects by Ids + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + var idsArray = ids.ToArray(); + if (idsArray.Length == 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // ensure that the result has the order based on the ids passed in + var result = repository.GetAll(idsArray); + var content = result.ToDictionary(x => x.Id, x => x); + + var sortedResult = idsArray.Select(x => + { + IContent c; + return content.TryGetValue(x, out c) ? c : null; + }).WhereNotNull(); + + return sortedResult; + } + } + + /// + /// Gets objects by Ids + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + var idsArray = ids.ToArray(); + if (idsArray.Length == 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // ensure that the result has the order based on the ids passed in + var result = repository.GetAll(idsArray); + var content = result.ToDictionary(x => x.Key, x => x); + + var sortedResult = idsArray.Select(x => + { + IContent c; + return content.TryGetValue(x, out c) ? c : null; + }).WhereNotNull(); + + return sortedResult; + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + public IContent GetById(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Get(key); + } + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetContentOfContentType(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ContentTypeId == id); + return repository.GetByQuery(query); + } + } + + internal IEnumerable GetPublishedContentOfContentType(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ContentTypeId == id); + return repository.GetByPublishedVersion(query); + } + } + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + public IEnumerable GetByLevel(int level) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IContent GetByVersion(Guid versionId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetByVersion(versionId); + } + } + + + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetAllVersions(id); + } + } + + /// + /// Gets a list of all version Ids for the given content item ordered so latest is first + /// + /// + /// The maximum number of rows to return + /// + public IEnumerable GetVersionIds(int id, int maxRows) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetVersionIds(id, maxRows); + } + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) + { + var content = GetById(id); + return GetAncestors(content); + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IContent content) + { + //null check otherwise we get exceptions + if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); + + var ids = content.Path.Split(',').Where(x => x != Constants.System.Root.ToInvariantString() && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + if (ids.Any() == false) + return new List(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + public IEnumerable GetChildren(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == id); + return repository.GetByQuery(query).OrderBy(x => x.SortOrder); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + long total; + var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder; + // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method) + query.Where(x => x.ParentId == id); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + long total; + var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, string filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // get query - if the id is System Root, then just get all + var query = Query.Builder; + if (id != Constants.System.Root) + { + var entityRepository = RepositoryFactory.CreateEntityRepository(uow); + var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); + if (contentPath.Length == 0) + { + totalChildren = 0; + return Enumerable.Empty(); + } + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); + } + + + // get filter + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // get query - if the id is System Root, then just get all + var query = Query.Builder; + if (id != Constants.System.Root) + { + var entityRepository = RepositoryFactory.CreateEntityRepository(uow); + var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); + if (contentPath.Length == 0) + { + totalChildren = 0; + return Enumerable.Empty(); + } + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + } + } + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + public IEnumerable GetChildrenByName(int parentId, string name) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(int id) + { + var content = GetById(id); + return content == null ? Enumerable.Empty() : GetDescendants(content); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(IContent content) + { + //This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption + if (content.ValidatePath() == false) + throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId)); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var pathMatch = content.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); + return repository.GetByQuery(query); + } + } + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IContent GetParent(int id) + { + var content = GetById(id); + return GetParent(content); + } + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + public IContent GetParent(IContent content) + { + if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) + return null; + + return GetById(content.ParentId); + } + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + public IContent GetPublishedVersion(int id) + { + var version = GetVersions(id); + return version.FirstOrDefault(x => x.Published); + } + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + public IContent GetPublishedVersion(IContent content) + { + if (content.Published) return content; + return content.HasPublishedVersion + ? GetByVersion(content.PublishedVersionGuid) + : null; + } + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootContent() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all published content items + /// + /// + internal IEnumerable GetAllPublished() + { + //create it once if it is needed (no need for locking here) + if (_notTrashedQuery == null) + { + _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); + } + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetByPublishedVersion(_notTrashedQuery); + } + } + + /// + /// Gets a collection of objects, which has an expiration date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForExpiration() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); + return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); + } + } + + /// + /// Gets a collection of objects, which has a release date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForRelease() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetContentInRecycleBin() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); + return repository.GetByQuery(query); + } + } + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + public bool HasChildren(int id) + { + return CountChildren(id) > 0; + } + + internal int CountChildren(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == id); + return repository.Count(query); + } + } + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + public bool HasPublishedVersion(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); + return repository.Count(query) > 0; + } + } + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// True if the Content can be published, otherwise False + public bool IsPublishable(IContent content) + { + int[] ids; + if (content.HasIdentity) + { + // get ids from path (we have identity) + // skip the first one that has to be -1 - and we don't care + // skip the last one that has to be "this" - and it's ok to stop at the parent + ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray(); + } + else + { + // no path yet (no identity), have to move up to parent + // skip the first one that has to be -1 - and we don't care + // don't skip the last one that is "parent" + var parent = GetById(content.ParentId); + if (parent == null) return false; + ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray(); + } + if (ids.Length == 0) + return false; + + // if the first one is recycle bin, fail fast + if (ids[0] == Constants.System.RecycleBinContent) + return false; + + // fixme - move to repository? + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql(@" + SELECT id + FROM umbracoNode + JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0 + WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)", + true, false, ids); + var x = uow.Database.Fetch(sql); + return ids.Length == x.Count; + } + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// This is not used for anything + /// True if publishing succeeded, otherwise False + /// + /// This is used for when a document type alias or a document type property is changed, the xml will need to + /// be regenerated. + /// + public bool RePublishAll(int userId = 0) + { + try + { + RebuildXmlStructures(); + return true; + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + return false; + } + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// + /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure + /// for all published content. + /// + internal void RePublishAll(params int[] contentTypeIds) + { + try + { + RebuildXmlStructures(contentTypeIds); + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + } + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public bool Publish(IContent content, int userId = 0) + { + var result = SaveAndPublishDo(content, userId); + Logger.Info("Call was made to ContentService.Publish, use PublishWithStatus instead since that method will provide more detailed information on the outcome"); + return result.Success; + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> IContentServiceOperations.PublishWithChildren(IContent content, int userId, bool includeUnpublished) + { + return PublishWithChildrenDo(content, userId, includeUnpublished); + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt IContentServiceOperations.SaveAndPublish(IContent content, int userId, bool raiseEvents) + { + return SaveAndPublishDo(content, userId, raiseEvents); + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + Attempt IContentServiceOperations.MoveToRecycleBin(IContent content, int userId) + { + return MoveToRecycleBinDo(content, userId, false); + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + /// + /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method + /// because it has already looked up all descendant nodes that will need to be recycled + /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations + /// + private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants) + { + var evtMsgs = EventMessagesFactory.Get(); + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + var originalPath = content.Path; + var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent); + var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); + if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + var moveInfo = new List> + { + moveEventInfo + }; + + //get descendents to process of the content item that is being moved to trash - must be done before changing the state below + //must be processed with shallowest levels first + var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level); + + //Do the updates for this item + var repository = RepositoryFactory.CreateContentRepository(uow); + //Make sure that published content is unpublished before being moved to the Recycle Bin + if (HasPublishedVersion(content.Id)) + { + //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! + UnPublish(content, userId); + } + content.WriterId = userId; + content.ChangeTrashedState(true); + repository.AddOrUpdate(content); + + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId + foreach (var descendant in descendants) + { + //TODO: this shouldn't be a 'sub operation', and if it needs to be it cannot raise events and cannot be cancelled! + UnPublish(descendant, userId); + descendant.WriterId = userId; + descendant.ChangeTrashedState(true, descendant.ParentId); + repository.AddOrUpdate(descendant); + + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + } + + moveEventArgs.CanCancel = false; + moveEventArgs.MoveInfoCollection = moveInfo; + uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed"); + + Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); + uow.Commit(); + } + + return OperationStatus.Success(evtMsgs); + } + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt IContentServiceOperations.UnPublish(IContent content, int userId) + { + return UnPublishDo(content, false, userId); + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + public Attempt PublishWithStatus(IContent content, int userId = 0) + { + return ((IContentServiceOperations)this).Publish(content, userId); + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] + public bool PublishWithChildren(IContent content, int userId = 0) + { + var result = PublishWithChildrenDo(content, userId, true); + + //This used to just return false only when the parent content failed, otherwise would always return true so we'll + // do the same thing for the moment + if (result.All(x => x.Result.ContentItem.Id != content.Id)) + return false; + + return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// set to true if you want to also publish children that are currently unpublished + /// True if publishing succeeded, otherwise False + public IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false) + { + return ((IContentServiceOperations)this).PublishWithChildren(content, userId, includeUnpublished); + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + public bool UnPublish(IContent content, int userId = 0) + { + var attempt = ((IContentServiceOperations)this).UnPublish(content, userId); + LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType)); + return attempt.Success; + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) + { + var result = SaveAndPublishDo(content, userId, raiseEvents); + return result.Success; + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + { + return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); + } + + public IContent GetBlueprintById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content) blueprint).IsBlueprint = true; + return blueprint; + } + } + + public IContent GetBlueprintById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content)blueprint).IsBlueprint = true; + return blueprint; + } + } + + public void SaveBlueprint(IContent content, int userId = 0) + { + //always ensure the blueprint is at the root + if (content.ParentId != -1) + content.ParentId = -1; + + ((Content) content).IsBlueprint = true; + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content blueprint with empty name."); + } + + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + if (content.HasIdentity == false) + { + content.CreatorId = userId; + } + content.WriterId = userId; + + repository.AddOrUpdate(content); + + uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); + + uow.Commit(); + } + } + } + + public void DeleteBlueprint(IContent content, int userId = 0) + { + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + repository.Delete(content); + uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint"); + uow.Commit(); + } + } + } + + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) + { + if (blueprint == null) throw new ArgumentNullException("blueprint"); + + var contentType = blueprint.ContentType; + var content = new Content(name, -1, contentType); + content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); + + content.CreatorId = userId; + content.WriterId = userId; + + foreach (var property in blueprint.Properties) + content.SetValue(property.Alias, property.Value); + + return content; + } + + public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0) + { + using (new WriteLock(Locker)) + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + var contentTypeIdsA = contentTypeIds.ToArray(); + var query = new Query(); + if (contentTypeIdsA.Length > 0) + { + query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); + } + var blueprints = repository.GetByQuery(query).Select(x => + { + ((Content) x).IsBlueprint = true; + return x; + }).ToArray(); + + foreach (var blueprint in blueprints) + { + repository.Delete(blueprint); + } + + uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint"); + uow.Commit(); + } + } + + public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0) + { + DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId); + } + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IContent content, int userId = 0, bool raiseEvents = true) + { + ((IContentServiceOperations)this).Save(content, userId, raiseEvents); + } + + /// + /// Saves a collection of objects. + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt IContentServiceOperations.Save(IEnumerable contents, int userId, bool raiseEvents) + { + var asArray = contents.ToArray(); + + var evtMsgs = EventMessagesFactory.Get(); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(asArray, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + + // todo - understand what's a lock in a scope? + // (though, these locks are refactored in v8) + using (new WriteLock(Locker)) + { + var containsNew = asArray.Any(x => x.HasIdentity == false); + var repository = RepositoryFactory.CreateContentRepository(uow); + + if (containsNew) + { + foreach (var content in asArray) + { + content.WriterId = userId; + + //Only change the publish state if the "previous" version was actually published + if (content.Published) + content.ChangePublishedState(PublishedState.Saved); + + repository.AddOrUpdate(content); + //add or update preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + } + else + { + foreach (var content in asArray) + { + content.WriterId = userId; + repository.AddOrUpdate(content); + //add or update preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + } + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); + uow.Commit(); + + return OperationStatus.Success(evtMsgs); + } + } + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + Attempt IContentServiceOperations.Delete(IContent content, int userId) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); + if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs, "Deleting")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + + //Make sure that published content is unpublished before being deleted + if (HasPublishedVersion(content.Id)) + { + UnPublish(content, userId); + } + + //Delete children before deleting the 'possible parent' + var children = GetChildren(content.Id); + foreach (var child in children) + { + Delete(child, userId); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + repository.Delete(content); + + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(Deleted, this, deleteEventArgs, "Deleted"); + + Audit(uow, AuditType.Delete, "Delete Content performed by user", userId, content.Id); + uow.Commit(); + } + + return OperationStatus.Success(evtMsgs); + } + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt IContentServiceOperations.Publish(IContent content, int userId) + { + return SaveAndPublishDo(content, userId); + } + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt IContentServiceOperations.Save(IContent content, int userId, bool raiseEvents) + { + return Save(content, true, userId, raiseEvents); + } + + /// + /// Saves a collection of objects. + /// + /// + /// If the collection of content contains new objects that references eachother by Id or ParentId, + /// then use the overload Save method with a collection of Lazy . + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true) + { + ((IContentServiceOperations)this).Save(contents, userId, raiseEvents); + } + + /// + /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. + /// + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) + { + using (new WriteLock(Locker)) + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + //track the 'root' items of the collection of nodes discovered to delete, we need to use + //these items to lookup descendants that are not of this doc type so they can be transfered + //to the recycle bin + IDictionary rootItems; + var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray(); + + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting")) + { + uow.Commit(); + return; + } + + //Determine the items that will need to be recycled (that are children of these content items but not of these content types) + var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository); + + //move each item to the bin starting with the deepest items + foreach (var child in contentToRecycle.OrderByDescending(x => x.Level)) + { + MoveToRecycleBinDo(child, userId, true); + } + + foreach (var content in contentToDelete) + { + Delete(content, userId); + } + + Audit(uow, AuditType.Delete, + string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)), + userId, Constants.System.Root); + uow.Commit(); + } + } + + /// + /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfType(int contentTypeId, int userId = 0) + { + DeleteContentOfTypes(new[] {contentTypeId}, userId); + } + + /// + /// Permanently deletes an object as well as all of its Children. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + public void Delete(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).Delete(content, userId); + } + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); + if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs, "DeletingVersions")) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.DeleteVersions(id, versionDate); + deleteRevisionsEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs, "DeletedVersions"); + + Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); + uow.Commit(); + } + } + + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) + { + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions")) + { + uow.Commit(); + return; + } + + if (deletePriorVersions) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.DeleteVersion(versionId); + + uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions"); + + Audit(uow, AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + uow.Commit(); + } + } + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + public void MoveToRecycleBin(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); + } + + /// + /// Moves an object to a new location by changing its parent id. + /// + /// + /// If the object is already published it will be + /// published after being moved to its new location. Otherwise it'll just + /// be saved with a new parent id. + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + public void Move(IContent content, int parentId, int userId = 0) + { + using (new WriteLock(Locker)) + { + //This ensures that the correct method is called if this method is used to Move to recycle bin. + if (parentId == Constants.System.RecycleBinContent) + { + MoveToRecycleBin(content, userId); + return; + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); + var moveEventArgs = new MoveEventArgs(moveEventInfo); + if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs, "Moving")) + { + uow.Commit(); + return; + } + + //used to track all the moved entities to be given to the event + var moveInfo = new List>(); + + //call private method that does the recursive moving + PerformMove(content, parentId, userId, moveInfo); + + moveEventArgs.MoveInfoCollection = moveInfo; + moveEventArgs.CanCancel = false; + uow.Events.Dispatch(Moved, this, moveEventArgs, "Moved"); + + Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id); + uow.Commit(); + } + } + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] + public void EmptyRecycleBin() => EmptyRecycleBin(0); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + public void EmptyRecycleBin(int userId = 0) + { + using (new WriteLock(Locker)) + { + var nodeObjectType = Constants.ObjectTypes.DocumentGuid; + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + //Create a dictionary of ids -> dictionary of property aliases + values + var entities = repository.GetEntitiesInRecycleBin() + .ToDictionary( + key => key.Id, + val => (IEnumerable)val.Properties); + + var files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); + + var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, entities, files); + if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) + { + uow.Commit(); + return; + } + + var success = repository.EmptyRecycleBin(); + recycleBinEventArgs.CanCancel = false; + recycleBinEventArgs.RecycleBinEmptiedSuccessfully = success; + uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); + + Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", userId, Constants.System.RecycleBinContent); + uow.Commit(); + } + } + } + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. Recursively copies all children. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) + { + return Copy(content, parentId, relateToOriginal, true, userId); + } + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0) + { + //TODO: This all needs to be managed correctly so that the logic is submitted in one + // transaction, the CRUD needs to be moved to the repo + + using (new WriteLock(Locker)) + { + var copy = content.DeepCloneWithResetIdentities(); + copy.ParentId = parentId; + + // A copy should never be set to published automatically even if the original was. + copy.ChangePublishedState(PublishedState.Unpublished); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); + if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs)) + { + uow.Commit(); + return null; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + // Update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; + + //get the current permissions, if there are any explicit ones they need to be copied + var currentPermissions = GetPermissionsForEntity(content); + currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); + + repository.AddOrUpdate(copy); + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + //add permissions + if (currentPermissions.Count > 0) + { + var permissionSet = new ContentPermissionSet(copy, currentPermissions); + repository.AddOrUpdatePermissions(permissionSet); + } + + uow.Commit(); // todo - this should flush, not commit + + //Special case for the associated tags + //TODO: Move this to the repository layer in a single transaction! + //don't copy tags data in tags table if the item is in the recycle bin + if (parentId != Constants.System.RecycleBinContent) + { + var tags = uow.Database.Fetch("WHERE nodeId = @Id", new { Id = content.Id }); + foreach (var tag in tags) + uow.Database.Insert(new TagRelationshipDto + { + NodeId = copy.Id, TagId = tag.TagId, PropertyTypeId = tag.PropertyTypeId + }); + } + uow.Commit(); // todo - this should flush, not commit + + if (recursive) + { + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + //TODO: This shouldn't recurse back to this method, it should be done in a private method + // that doesn't have a nested lock and so we can perform the entire operation in one commit. + Copy(child, copy.Id, relateToOriginal, true, userId); + } + } + copyEventArgs.CanCancel = false; + uow.Events.Dispatch(Copied, this, copyEventArgs); + Audit(uow, AuditType.Copy, "Copy Content performed by user", userId, content.Id); + uow.Commit(); + } + + return copy; + } + } + + + /// + /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. + /// + /// The to send to publication + /// Optional Id of the User issueing the send to publication + /// True if sending publication was succesfull otherwise false + public bool SendToPublication(IContent content, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var sendToPublishEventArgs = new SendToPublishEventArgs(content); + if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) + { + uow.Commit(); + return false; + } + + //Save before raising event + Save(content, userId); + sendToPublishEventArgs.CanCancel = false; + uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); + + Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + uow.Commit(); + + return true; + } + } + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// + /// The way data is stored actually only allows us to rollback on properties + /// and not data like Name and Alias of the Content. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + public IContent Rollback(int id, Guid versionId, int userId = 0) + { + var content = GetByVersion(versionId); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var rollbackEventArgs = new RollbackEventArgs(content); + if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + { + uow.Commit(); + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + content.WriterId = userId; + content.CreatorId = userId; + content.ChangePublishedState(PublishedState.Unpublished); + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + rollbackEventArgs.CanCancel = false; + uow.Events.Dispatch(RolledBack, this, rollbackEventArgs); + + Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); + uow.Commit(); + } + + return content; + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + { + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var asArray = items.ToArray(); + var saveEventArgs = new SaveEventArgs(asArray); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return false; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + var i = 0; + foreach (var content in asArray) + { + //If the current sort order equals that of the content + //we don't need to update it, so just increment the sort order + //and continue. + if (content.SortOrder == i) + { + i++; + continue; + } + + content.SortOrder = i; + content.WriterId = userId; + i++; + + if (content.Published) + { + //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! + var published = _publishingStrategy.Publish(uow, content, userId).Success; + shouldBePublished.Add(content); + } + else + shouldBeSaved.Add(content); + + repository.AddOrUpdate(content); + //add or update a preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + foreach (var content in shouldBePublished) + { + //Create and Save ContentXml DTO + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + if (shouldBePublished.Any()) + { + //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! + _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); + } + + Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); + uow.Commit(); + } + } + + return true; + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of node Ids passed in. + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + public bool Sort(int[] ids, int userId = 0, bool raiseEvents = true) + { + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + using (new WriteLock(Locker)) + { + var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); + if (allContent.Any() == false) + { + return false; + } + var items = ids.Select(x => allContent[x]).ToArray(); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(items); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return false; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + var i = 0; + foreach (var content in items) + { + //If the current sort order equals that of the content + //we don't need to update it, so just increment the sort order + //and continue. + if (content.SortOrder == i) + { + i++; + continue; + } + + content.SortOrder = i; + content.WriterId = userId; + i++; + + if (content.Published) + { + //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! + var published = _publishingStrategy.Publish(uow, content, userId).Success; + shouldBePublished.Add(content); + } + else + shouldBeSaved.Add(content); + + repository.AddOrUpdate(content); + //add or update a preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + foreach (var content in shouldBePublished) + { + //Create and Save ContentXml DTO + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + if (shouldBePublished.Any()) + { + //TODO: This should not be an inner operation, but if we do this, it cannot raise events and cannot be cancellable! + _publishingStrategy.PublishingFinalized(uow, shouldBePublished, false); + } + + Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); + uow.Commit(); + } + } + + return true; + } + + public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + var query = new Query(); + if (documentTypeIds.Length > 0) + { + query.Where(x => documentTypeIds.Contains(x.ContentTypeId)); + } + return repository.GetByQuery(query).Select(x => + { + ((Content) x).IsBlueprint = true; + return x; + }); + } + } + + /// + /// Gets paged content descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, + //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and + // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order + new[] { "level", "parentID", "sortOrder" }, + out totalRecords); + return contents; + } + } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var result = repository.BuildXmlCache(); + uow.Commit(); + return result; + } + } + + public XmlDocument BuildPreviewXmlCache() + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var result = repository.BuildPreviewXmlCache(); + uow.Commit(); + return result; + } + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all documents + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all content + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + repository.RebuildXmlStructures( + content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + + Audit(uow, AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); + uow.Commit(); + } + } + + #region Internal Methods + + /// + /// Gets a collection of descendants by the first Parent. + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + internal IEnumerable GetPublishedDescendants(IContent content) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); + return repository.GetByPublishedVersion(query); + } + } + + #endregion + + #region Private Methods + + /// + /// Hack: This is used to fix some data if an entity's properties are invalid/corrupt + /// + /// + private void QuickUpdate(IContent content) + { + if (content == null) throw new ArgumentNullException("content"); + if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity"); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.AddOrUpdate(content); + uow.Commit(); + } + } + + private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) + { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + } + + //TODO: All of this needs to be moved to the repository + private void PerformMove(IContent content, int parentId, int userId, ICollection> moveInfo) + { + //add a tracking item to use in the Moved event + moveInfo.Add(new MoveEventInfo(content, content.Path, parentId)); + + content.WriterId = userId; + if (parentId == Constants.System.Root) + { + content.Path = string.Concat(Constants.System.Root, ",", content.Id); + content.Level = 1; + } + else + { + var parent = GetById(parentId); + content.Path = string.Concat(parent.Path, ",", content.Id); + content.Level = parent.Level + 1; + } + + //If Content is being moved away from Recycle Bin, its state should be un-trashed + if (content.Trashed && parentId != Constants.System.RecycleBinContent) + { + content.ChangeTrashedState(false, parentId); + } + else + { + content.ParentId = parentId; + } + + //If Content is published, it should be (re)published from its new location + if (content.Published) + { + //If Content is Publishable its saved and published + //otherwise we save the content without changing the publish state, and generate new xml because the Path, Level and Parent has changed. + if (IsPublishable(content)) + { + //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine + SaveAndPublish(content, userId); + } + else + { + //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine + Save(content, false, userId); + + //TODO: This shouldn't be here! This needs to be part of the repository logic but in order to fix this we need to + // change how this method calls "Save" as it needs to save using an internal method + using (var uow = UowProvider.GetUnitOfWork()) + { + var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, content); + + var poco = new ContentXmlDto { NodeId = content.Id, Xml = xml.ToDataString() }; + var exists = + uow.Database.FirstOrDefault("WHERE nodeId = @Id", new { Id = content.Id }) != + null; + int result = exists + ? uow.Database.Update(poco) + : Convert.ToInt32(uow.Database.Insert(poco)); + uow.Commit(); + } + } + } + else + { + //TODO: This is raising events, probably not desirable as this costs performance for event listeners like Examine + Save(content, userId); + } + + //Ensure that Path and Level is updated on children + var children = GetChildren(content.Id).ToArray(); + if (children.Any()) + { + foreach (var child in children) + { + PerformMove(child, content.Id, userId, moveInfo); + } + } + } + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// + /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published + /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that + /// are to be published. + /// + private IEnumerable> PublishWithChildrenDo( + IContent content, int userId = 0, bool includeUnpublished = false) + { + if (content == null) throw new ArgumentNullException("content"); + + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + + var result = new List>(); + + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", + content.Name, content.Id)); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); + return result; + } + + //Content contains invalid property values and can therefore not be published - fire event? + if (!content.IsValid()) + { + Logger.Info( + string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", + content.Name, content.Id)); + result.Add( + Attempt.Fail( + new PublishStatus(content, PublishStatusType.FailedContentInvalid, evtMsgs) + { + InvalidProperties = ((ContentBase)content).LastInvalidProperties + })); + return result; + } + + //Consider creating a Path query instead of recursive method: + //var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); + + var updated = new List(); + var list = new List(); + list.Add(content); //include parent item + list.AddRange(GetDescendants(content)); + + var internalStrategy = _publishingStrategy; + + using (var uow = UowProvider.GetUnitOfWork()) + { + //Publish and then update the database with new status + var publishedOutcome = internalStrategy.PublishWithChildren(uow, list, userId, includeUnpublished).ToArray(); + var published = publishedOutcome + .Where(x => x.Success || x.Result.StatusType == PublishStatusType.SuccessAlreadyPublished) + // ensure proper order (for events) - cannot publish a child before its parent! + .OrderBy(x => x.Result.ContentItem.Level) + .ThenBy(x => x.Result.ContentItem.SortOrder); + + var repository = RepositoryFactory.CreateContentRepository(uow); + + //NOTE The Publish with subpages-dialog was used more as a republish-type-thing, so we'll have to include PublishStatusType.SuccessAlreadyPublished + //in the updated-list, so the Published event is triggered with the expected set of pages and the xml is updated. + foreach (var item in published) + { + item.Result.ContentItem.WriterId = userId; + repository.AddOrUpdate(item.Result.ContentItem); + //add or update a preview + repository.AddOrUpdatePreviewXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + //add or update the published xml + repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + updated.Add(item.Result.ContentItem); + } + + //Save xml to db and call following method to fire event: + _publishingStrategy.PublishingFinalized(uow, updated, false); + + Audit(uow, AuditType.Publish, "Publish with Children performed by user", userId, content.Id); + uow.Commit(); + + return publishedOutcome; + } + } + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + private Attempt UnPublishDo(IContent content, bool omitCacheRefresh = false, int userId = 0) + { + var newest = GetById(content.Id); // ensure we have the newest version + if (content.Version != newest.Version) // but use the original object if it's already the newest version + content = newest; + + var evtMsgs = EventMessagesFactory.Get(); + + var published = content.Published ? content : GetPublishedVersion(content.Id); // get the published version + if (published == null) + { + return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.SuccessAlreadyUnPublished, evtMsgs)); // already unpublished + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + var unpublished = _publishingStrategy.UnPublish(uow, content, userId); + if (unpublished == false) + { + uow.Commit(); + return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + content.WriterId = userId; + repository.AddOrUpdate(content); + // is published is not newest, reset the published flag on published version + if (published.Version != content.Version) + repository.ClearPublished(published); + repository.DeleteContentXml(content); + + //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache + if (omitCacheRefresh == false) + _publishingStrategy.UnPublishingFinalized(uow, content); + + Audit(uow, AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); + uow.Commit(); + } + + return Attempt.Succeed(new UnPublishStatus(content, UnPublishedStatusType.Success, evtMsgs)); + } + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(content, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); + } + + //Has this content item previously been published? If so, we don't need to refresh the children + var previouslyPublished = content.HasIdentity && HasPublishedVersion(content.Id); //content might not have an id + var publishStatus = new PublishStatus(content, PublishStatusType.Success, evtMsgs); //initially set to success + + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + publishStatus.StatusType = CheckAndLogIsPublishable(content); + //if it is not successful, then check if the props are valid + if ((int)publishStatus.StatusType < 10) + { + //Content contains invalid property values and can therefore not be published - fire event? + publishStatus.StatusType = CheckAndLogIsValid(content); + //set the invalid properties (if there are any) + publishStatus.InvalidProperties = ((ContentBase)content).LastInvalidProperties; + } + //if we're still successful, then publish using the strategy + if (publishStatus.StatusType == PublishStatusType.Success) + { + //Publish and then update the database with new status + var publishResult = _publishingStrategy.Publish(uow, content, userId); + //set the status type to the publish result + publishStatus.StatusType = publishResult.Result.StatusType; + } + + //we are successfully published if our publishStatus is still Successful + bool published = publishStatus.StatusType == PublishStatusType.Success; + + var repository = RepositoryFactory.CreateContentRepository(uow); + + if (published == false) + { + content.ChangePublishedState(PublishedState.Saved); + } + //Since this is the Save and Publish method, the content should be saved even though the publish fails or isn't allowed + if (content.HasIdentity == false) + { + content.CreatorId = userId; + } + content.WriterId = userId; + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + if (published) + { + //Content Xml + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + //Save xml to db and call following method to fire event through PublishingStrategy to update cache + if (published) + { + _publishingStrategy.PublishingFinalized(uow, content); + } + + //We need to check if children and their publish state to ensure that we 'republish' content that was previously published + if (published && previouslyPublished == false && HasChildren(content.Id)) + { + //TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy + var descendants = GetPublishedDescendants(content); + _publishingStrategy.PublishingFinalized(uow, descendants, false); + } + + uow.Commit(); + + if (publishStatus.StatusType == PublishStatusType.Success) + { + Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id); + } + else + { + Audit(uow, AuditType.Save, "Save performed by user", userId, content.Id); + } + uow.Commit(); + + return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); + } + } + } + + /// + /// Saves a single object + /// + /// The to save + /// Boolean indicating whether or not to change the Published state upon saving + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + private Attempt Save(IContent content, bool changeState, int userId = 0, bool raiseEvents = true) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(content, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content with empty name."); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + if (content.HasIdentity == false) + { + content.CreatorId = userId; + } + content.WriterId = userId; + + //Only change the publish state if the "previous" version was actually published or marked as unpublished + if (changeState && (content.Published || ((Content)content).PublishedState == PublishedState.Unpublished)) + content.ChangePublishedState(PublishedState.Saved); + + repository.AddOrUpdate(content); + + //Generate a new preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id); + uow.Commit(); + } + + return OperationStatus.Success(evtMsgs); + } + } + + private PublishStatusType CheckAndLogIsPublishable(IContent content) + { + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + if (content.ParentId != Constants.System.Root && content.ParentId != Constants.System.RecycleBinContent && IsPublishable(content) == false) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because its parent is not published.", + content.Name, content.Id)); + return PublishStatusType.FailedPathNotPublished; + } + + if (content.ExpireDate.HasValue && content.ExpireDate.Value > DateTime.MinValue && DateTime.Now > content.ExpireDate.Value) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' has expired and could not be published.", + content.Name, content.Id)); + return PublishStatusType.FailedHasExpired; + } + + if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' is awaiting release and could not be published.", + content.Name, content.Id)); + return PublishStatusType.FailedAwaitingRelease; + } + + return PublishStatusType.Success; + } + + private PublishStatusType CheckAndLogIsValid(IContent content) + { + //Content contains invalid property values and can therefore not be published - fire event? + if (content.IsValid() == false) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because of invalid properties.", + content.Name, content.Id)); + return PublishStatusType.FailedContentInvalid; + } + + return PublishStatusType.Success; + } + + private IContentType FindContentTypeByAlias(string contentTypeAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); + var types = repository.GetByQuery(query); + + if (types.Any() == false) + throw new Exception( + string.Format("No ContentType matching the passed in Alias: '{0}' was found", + contentTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", + contentTypeAlias)); + return contentType; + } + } + + #endregion + + #region Proxy Event Handlers + /// + /// Occurs before publish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> Publishing + { + add { PublishingStrategy.Publishing += value; } + remove { PublishingStrategy.Publishing -= value; } + } + + /// + /// Occurs after publish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> Published + { + add { PublishingStrategy.Published += value; } + remove { PublishingStrategy.Published -= value; } + } + /// + /// Occurs before unpublish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> UnPublishing + { + add { PublishingStrategy.UnPublishing += value; } + remove { PublishingStrategy.UnPublishing -= value; } + } + + /// + /// Occurs after unpublish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> UnPublished + { + add { PublishingStrategy.UnPublished += value; } + remove { PublishingStrategy.UnPublished -= value; } + } + #endregion + + #region Event Handlers + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Delete Versions + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete Versions + /// + public static event TypedEventHandler DeletedVersions; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Create + /// + [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] + public static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Content object has been created, but might not have been saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs before Copy + /// + public static event TypedEventHandler> Copying; + + /// + /// Occurs after Copy + /// + public static event TypedEventHandler> Copied; + + /// + /// Occurs before Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashing; + + /// + /// Occurs after Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashed; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + + /// + /// Occurs before Rollback + /// + public static event TypedEventHandler> RollingBack; + + /// + /// Occurs after Rollback + /// + public static event TypedEventHandler> RolledBack; + + /// + /// Occurs before Send to Publish + /// + public static event TypedEventHandler> SendingToPublish; + + /// + /// Occurs after Send to Publish + /// + public static event TypedEventHandler> SentToPublish; + + /// + /// Occurs before the Recycle Bin is emptied + /// + public static event TypedEventHandler EmptyingRecycleBin; + + /// + /// Occurs after the Recycle Bin has been Emptied + /// + public static event TypedEventHandler EmptiedRecycleBin; + + /// + /// Occurs after a blueprint has been saved. + /// + public static event TypedEventHandler> SavedBlueprint; + + /// + /// Occurs after a blueprint has been deleted. + /// + public static event TypedEventHandler> DeletedBlueprint; + + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 5a626c5d36..8c063890fd 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,717 +1,720 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Publishing; - -namespace Umbraco.Core.Services -{ - /// - /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented - /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. - /// - public interface IContentServiceOperations - { - //TODO: Remove this class in v8 - - //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Saves a collection of objects. - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// Permanently deletes an object. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - Attempt Delete(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt Publish(IContent content, int userId = 0); - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// - /// The list of statuses for all published items - IEnumerable> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - Attempt SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - Attempt MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - Attempt UnPublish(IContent content, int userId = 0); - } - - /// - /// Defines the ContentService, which is an easy access to operations involving - /// - public interface IContentService : IContentServiceBase - { - IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds); - IContent GetBlueprintById(int id); - IContent GetBlueprintById(Guid id); - void SaveBlueprint(IContent content, int userId = 0); - void DeleteBlueprint(IContent content, int userId = 0); - IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); - void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); - void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); - - /// - /// Gets all XML entries found in the cmsContentXml table based on the given path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of content items - /// - /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path - /// - IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); - - /// - /// This builds the Xml document used for the XML cache - /// - /// - XmlDocument BuildXmlCache(); - - XmlDocument BuildPreviewXmlCache(); - - /// - /// Rebuilds all xml content in the cmsContentXml table for all documents - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all content - /// - void RebuildXmlStructures(params int[] contentTypeIds); - - int CountPublished(string contentTypeAlias = null); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); - - /// - /// Used to bulk update the permissions set for a content item. This will replace all permissions - /// assigned to an entity with a list of user group id & permission pairs. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); - - /// - /// Assigns a single permission to the current content item for the specified user group ids - /// - /// - /// - /// - void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); - - /// - /// Returns implicit/inherited permissions assigned to the content item for all user groups - /// - /// - /// - EntityPermissionCollection GetPermissionsForEntity(IContent content); - - bool SendToPublication(IContent content, int userId = 0); - - IEnumerable GetByIds(IEnumerable ids); - IEnumerable GetByIds(IEnumerable ids); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// Note that using this method will simply return a new IContent without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - IContent GetById(int id); - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - IContent GetById(Guid key); - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetContentOfContentType(int id); - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - IEnumerable GetByLevel(int level); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets a collection of an objects versions by its Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); - - /// - /// Gets a list of all version Ids for the given content item ordered so latest is first - /// - /// - /// The maximum number of rows to return - /// - IEnumerable GetVersionIds(int id, int maxRows); - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - IEnumerable GetRootContent(); - - /// - /// Gets a collection of objects, which has an expiration date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForExpiration(); - - /// - /// Gets a collection of objects, which has a release date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForRelease(); - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - IEnumerable GetContentInRecycleBin(); - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - void Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Saves a collection of objects. - /// - /// Collection of to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional Id of the user issueing the delete operation - void DeleteContentOfType(int contentTypeId, int userId = 0); - - /// - /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Ids of the s - /// Optional Id of the user issueing the delete operation - void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0); - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); - - /// - /// Permanently deletes a specific version from an object. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// Move an item to the Recycle Bin will result in the item being unpublished - /// The to delete - /// Optional Id of the User deleting the Content - void MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - void Move(IContent content, int parentId, int userId = 0); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - void EmptyRecycleBin(); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - void EmptyRecycleBin(int userId = 0); - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - IContent Rollback(int id, Guid versionId, int userId = 0); - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - IEnumerable GetChildrenByName(int parentId, string name); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(int id); - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(IContent content); - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - IContent GetByVersion(Guid versionId); - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - IContent GetPublishedVersion(int id); - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - IContent GetPublishedVersion(IContent content); - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - bool HasChildren(int id); - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - bool HasPublishedVersion(int id); - - /// - /// Re-Publishes all Content - /// - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool RePublishAll(int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool Publish(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt PublishWithStatus(IContent content, int userId = 0); - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] - bool PublishWithChildren(IContent content, int userId = 0); - - /// - /// Publishes a object and all its children - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// - /// The list of statuses for all published items - IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - bool UnPublish(IContent content, int userId = 0); - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - [EditorBrowsable(EditorBrowsableState.Never)] - bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Saves and Publishes a single object - /// - /// The to save and publish - /// Optional Id of the User issueing the publishing - /// Optional boolean indicating whether or not to raise save events. - /// True if publishing succeeded, otherwise False - Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// Permanently deletes an object. - /// - /// - /// This method will also delete associated media files, child content and possibly associated domains. - /// - /// Please note that this method will completely remove the Content from the database - /// The to delete - /// Optional Id of the User deleting the Content - void Delete(IContent content, int userId = 0); - - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy, which is returned. Recursively copies all children. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); - - /// - /// Copies an object by creating a new Content object of the same type and copies all data from the current - /// to the new copy which is returned. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// True if the Content can be published, otherwise False - bool IsPublishable(IContent content); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(int id); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(IContent content); - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of node Ids passed in. - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(int[] ids, int userId = 0, bool raiseEvents = true); - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - IContent GetParent(int id); - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - IContent GetParent(IContent content); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Publishing; + +namespace Umbraco.Core.Services +{ + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IContentServiceOperations + { + //TODO: Remove this class in v8 + + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Saves a collection of objects. + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + Attempt Delete(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt Publish(IContent content, int userId = 0); + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + Attempt MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt UnPublish(IContent content, int userId = 0); + } + + /// + /// Defines the ContentService, which is an easy access to operations involving + /// + public interface IContentService : IContentServiceBase + { + IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds); + IContent GetBlueprintById(int id); + IContent GetBlueprintById(Guid id); + void SaveBlueprint(IContent content, int userId = 0); + void DeleteBlueprint(IContent content, int userId = 0); + IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); + void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); + void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); + + /// + /// Gets all XML entries found in the cmsContentXml table based on the given path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + /// + /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path + /// + IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); + + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + + XmlDocument BuildPreviewXmlCache(); + + /// + /// Rebuilds all xml content in the cmsContentXml table for all documents + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all content + /// + void RebuildXmlStructures(params int[] contentTypeIds); + + int CountPublished(string contentTypeAlias = null); + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); + + /// + /// Used to bulk update the permissions set for a content item. This will replace all permissions + /// assigned to an entity with a list of user group id & permission pairs. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a single permission to the current content item for the specified user group ids + /// + /// + /// + /// + void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); + + /// + /// Returns implicit/inherited permissions assigned to the content item for all user groups + /// + /// + /// + EntityPermissionCollection GetPermissionsForEntity(IContent content); + + bool SendToPublication(IContent content, int userId = 0); + + IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + IContent GetById(int id); + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + IContent GetById(Guid key); + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + IEnumerable GetContentOfContentType(int id); + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + IEnumerable GetByLevel(int level); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + IEnumerable GetChildren(int id); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + + /// + /// Gets a collection of an objects versions by its Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); + + /// + /// Gets a list of all version Ids for the given content item ordered so latest is first + /// + /// + /// The maximum number of rows to return + /// + IEnumerable GetVersionIds(int id, int maxRows); + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + IEnumerable GetRootContent(); + + /// + /// Gets a collection of objects, which has an expiration date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForExpiration(); + + /// + /// Gets a collection of objects, which has a release date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForRelease(); + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + IEnumerable GetContentInRecycleBin(); + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + void Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Saves a collection of objects. + /// + /// Collection of to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// Deletes all content of specified type. All children of deleted content is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional Id of the user issueing the delete operation + void DeleteContentOfType(int contentTypeId, int userId = 0); + + /// + /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Ids of the s + /// Optional Id of the user issueing the delete operation + void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0); + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + void DeleteVersions(int id, DateTime versionDate, int userId = 0); + + /// + /// Permanently deletes a specific version from an object. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// Move an item to the Recycle Bin will result in the item being unpublished + /// The to delete + /// Optional Id of the User deleting the Content + void MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + void Move(IContent content, int parentId, int userId = 0); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] + void EmptyRecycleBin(); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + void EmptyRecycleBin(int userId = 0); + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + IContent Rollback(int id, Guid versionId, int userId = 0); + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + IEnumerable GetChildrenByName(int parentId, string name); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(int id); + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(IContent content); + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + IContent GetByVersion(Guid versionId); + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + IContent GetPublishedVersion(int id); + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + IContent GetPublishedVersion(IContent content); + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + bool HasChildren(int id); + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + bool HasPublishedVersion(int id); + + /// + /// Re-Publishes all Content + /// + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool RePublishAll(int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool Publish(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt PublishWithStatus(IContent content, int userId = 0); + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] + bool PublishWithChildren(IContent content, int userId = 0); + + /// + /// Publishes a object and all its children + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// + /// The list of statuses for all published items + IEnumerable> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + bool UnPublish(IContent content, int userId = 0); + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + [EditorBrowsable(EditorBrowsableState.Never)] + bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Saves and Publishes a single object + /// + /// The to save and publish + /// Optional Id of the User issueing the publishing + /// Optional boolean indicating whether or not to raise save events. + /// True if publishing succeeded, otherwise False + Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// Permanently deletes an object. + /// + /// + /// This method will also delete associated media files, child content and possibly associated domains. + /// + /// Please note that this method will completely remove the Content from the database + /// The to delete + /// Optional Id of the User deleting the Content + void Delete(IContent content, int userId = 0); + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy, which is returned. Recursively copies all children. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + + /// + /// Copies an object by creating a new Content object of the same type and copies all data from the current + /// to the new copy which is returned. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// True if the Content can be published, otherwise False + bool IsPublishable(IContent content); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(int id); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(IContent content); + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of node Ids passed in. + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(int[] ids, int userId = 0, bool raiseEvents = true); + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + IContent GetParent(int id); + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + IContent GetParent(IContent content); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); + + IList GetAnchorValuesFromRTEs(int id); + IList GetAnchorValuesFromRTEContent(string rteContent); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 31ff7424ac..db5e9dd404 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -160,6 +160,38 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for id ' + id); }, + + getUrlAndAnchors: function (id) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrlAndAnchors", + [{ id: id }])), + 'Failed to retrieve url and anchors data for id ' + id); + }, + + + getAnchors: function (rteContent) { + + if (rteContent == null || rteContent.length === 0) { + return []; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAnchors", + [{ rteContent: rteContent }])), + 'Failed to anchors data for rte content ' + rteContent); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds @@ -488,6 +520,7 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve child data for id ' + parentId); }, + /** * @ngdoc method * @name umbraco.resources.entityResource#search @@ -583,6 +616,8 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for query ' + query); } + + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 7edd462a57..f721c31337 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1,879 +1,850 @@ -/** - * @ngdoc service - * @name umbraco.services.tinyMceService - * - * - * @description - * A service containing all logic for all of the Umbraco TinyMCE plugins - */ -function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { - return { - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#configuration - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a collection of plugins available to the tinyMCE editor - * - */ - configuration: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "rteApiBaseUrl", - "GetConfiguration"), { - cache: true - }), - 'Failed to retrieve tinymce configuration'); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#defaultPrevalues - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a default configration to fallback on in case none is provided - * - */ - defaultPrevalues: function () { - var cfg = {}; - cfg.toolbar = ["code", "bold", "italic", "styleselect", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; - cfg.stylesheets = []; - cfg.dimensions = { - height: 500 - }; - cfg.maxImageSize = 500; - return cfg; - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert embedded media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertEmbeddedMedia: function (editor, scope, callback) { - editor.addButton('umbembeddialog', { - icon: 'custom icon-tv', - tooltip: 'Embed', - onclick: function () { - if (callback) { - callback(); - } - } - }); - }, - - insertEmbeddedMediaInEditor: function (editor, preview) { - editor.insertContent(preview); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createMediaPicker - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createMediaPicker: function (editor, scope, callback) { - editor.addButton('umbmediapicker', { - icon: 'custom icon-picture', - tooltip: 'Media Picker', - stateSelector: 'img', - onclick: function () { - - var selectedElm = editor.selection.getNode(), - currentTarget; - - - if (selectedElm.nodeName === 'IMG') { - var img = $(selectedElm); - - var hasUdi = img.attr("data-udi") ? true : false; - - currentTarget = { - altText: img.attr("alt"), - url: img.attr("src") - }; - - if (hasUdi) { - currentTarget["udi"] = img.attr("data-udi"); - } else { - currentTarget["id"] = img.attr("rel"); - } - } - - userService.getCurrentUser().then(function (userData) { - if (callback) { - callback(currentTarget, userData); - } - }); - - } - }); - }, - - insertMediaInEditor: function (editor, img) { - if (img) { - - var hasUdi = img.udi ? true : false; - - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew' - }; - - if (hasUdi) { - data["data-udi"] = img.udi; - } else { - //Considering these fixed because UDI will now be used and thus - // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 - data["rel"] = img.id; - data["data-id"] = img.id; - } - - editor.insertContent(editor.dom.createHTML('img', data)); - - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - var size = editor.dom.getSize(imgElm); - - if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { - var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); - - var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; - editor.dom.setAttrib(imgElm, 'style', s); - - if (img.url) { - var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; - editor.dom.setAttrib(imgElm, 'data-mce-src', src); - } - } - editor.dom.setAttrib(imgElm, 'id', null); - }, 500); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createUmbracoMacro - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the insert umbrco macro tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertMacro: function (editor, $scope, callback) { - - var createInsertMacroScope = this; - - /** Adds custom rules for the macro plugin and custom serialization */ - editor.on('preInit', function (args) { - //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out - editor.serializer.addRules('div'); - - /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ - editor.serializer.addNodeFilter('div', function (nodes, name) { - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { - nodes[i].parent.unwrap(); - } - } - }); - - }); - - /** - * Because the macro gets wrapped in a P tag because of the way 'enter' works, this - * method will return the macro element if not wrapped in a p, or the p if the macro - * element is the only one inside of it even if we are deep inside an element inside the macro - */ - function getRealMacroElem(element) { - var e = $(element).closest(".umb-macro-holder"); - if (e.length > 0) { - if (e.get(0).parentNode.nodeName === "P") { - //now check if we're the only element - if (element.parentNode.childNodes.length === 1) { - return e.get(0).parentNode; - } - } - return e.get(0); - } - return null; - } - - /** Adds the button instance */ - editor.addButton('umbmacro', { - icon: 'custom icon-settings-alt', - tooltip: 'Insert macro', - onPostRender: function () { - - var ctrl = this; - var isOnMacroElement = false; - - /** - if the selection comes from a different element that is not the macro's - we need to check if the selection includes part of the macro, if so we'll force the selection - to clear to the next element since if people can select part of the macro markup they can then modify it. - */ - function handleSelectionChange() { - - if (!editor.selection.isCollapsed()) { - var endSelection = tinymce.activeEditor.selection.getEnd(); - var startSelection = tinymce.activeEditor.selection.getStart(); - //don't proceed if it's an entire element selected - if (endSelection !== startSelection) { - - //if the end selection is a macro then move the cursor - //NOTE: we don't have to handle when the selection comes from a previous parent because - // that is automatically taken care of with the normal onNodeChanged logic since the - // evt.element will be the macro once it becomes part of the selection. - var $testForMacro = $(endSelection).closest(".umb-macro-holder"); - if ($testForMacro.length > 0) { - - //it came from before so move after, if there is no after then select ourselves - var next = $testForMacro.next(); - if (next.length > 0) { - editor.selection.setCursorLocation($testForMacro.next().get(0)); - } else { - selectMacroElement($testForMacro.get(0)); - } - - } - } - } - } - - /** helper method to select the macro element */ - function selectMacroElement(macroElement) { - - // move selection to top element to ensure we can't edit this - editor.selection.select(macroElement); - - // check if the current selection *is* the element (ie bug) - var currentSelection = editor.selection.getStart(); - if (tinymce.isIE) { - if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { - while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { - currentSelection = currentSelection.parentNode; - } - editor.selection.select(currentSelection); - } - } - } - - /** - * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. - * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves - * from the event listener before changing selection, however, it seems that putting a break point in this method - * will always cause an 'infinite' loop as the caret keeps changing. - */ - function onNodeChanged(evt) { - - //set our macro button active when on a node of class umb-macro-holder - var $macroElement = $(evt.element).closest(".umb-macro-holder"); - - handleSelectionChange(); - - //set the button active - ctrl.active($macroElement.length !== 0); - - if ($macroElement.length > 0) { - var macroElement = $macroElement.get(0); - - //remove the event listener before re-selecting - editor.off('NodeChange', onNodeChanged); - - selectMacroElement(macroElement); - - //set the flag - isOnMacroElement = true; - - //re-add the event listener - editor.on('NodeChange', onNodeChanged); - } else { - isOnMacroElement = false; - } - - } - - /** when the contents load we need to find any macros declared and load in their content */ - editor.on("LoadContent", function (o) { - - //get all macro divs and load their content - $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { - createInsertMacroScope.loadMacroContent($(this), null, $scope); - }); - - }); - - /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ - editor.on('BeforeExecCommand', function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - /** This double checks and ensures you can't paste content into the rendered macro */ - editor.on("Paste", function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - //set onNodeChanged event listener - editor.on('NodeChange', onNodeChanged); - - /** - * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so - * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request - * so the macro cannot be edited. - */ - editor.on('KeyDown', function (e) { - if (isOnMacroElement) { - var macroElement = editor.selection.getNode(); - - //get the 'real' element (either p or the real one) - macroElement = getRealMacroElem(macroElement); - - //prevent editing - e.preventDefault(); - e.stopPropagation(); - - var moveSibling = function (element, isNext) { - var $e = $(element); - var $sibling = isNext ? $e.next() : $e.prev(); - if ($sibling.length > 0) { - editor.selection.select($sibling.get(0)); - editor.selection.collapse(true); - } else { - //if we're moving previous and there is no sibling, then lets recurse and just select the next one - if (!isNext) { - moveSibling(element, true); - return; - } - - //if there is no sibling we'll generate a new p at the end and select it - editor.setContent(editor.getContent() + "

 

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

 

"); + editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); + editor.selection.collapse(true); + + } + }; + + //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) + //supported keys to remove the macro (8-backspace, 46-delete) + //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? + if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { + //move to next element + moveSibling(macroElement, true); + } else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { + //move to prev element + moveSibling(macroElement, false); + } else if ($.inArray(e.keyCode, [8, 46]) !== -1) { + //delete macro element + + //move first, then delete + moveSibling(macroElement, false); + editor.dom.remove(macroElement); + } + return; + } + }); + + }, + + /** The insert macro button click event handler */ + onclick: function () { + + var dialogData = { + //flag for use in rte so we only show macros flagged for the editor + richTextEditor: true + }; + + //when we click we could have a macro already selected and in that case we'll want to edit the current parameters + //so we'll need to extract them and submit them to the dialog. + var macroElement = editor.selection.getNode(); + macroElement = getRealMacroElem(macroElement); + if (macroElement) { + //we have a macro selected so we'll need to parse it's alias and parameters + var contents = $(macroElement).contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + dialogData = { + macroData: parsed + }; + } + + if (callback) { + callback(dialogData); + } + + } + }); + }, + + insertMacroInEditor: function (editor, macroObject, $scope) { + + //put the macro syntax in comments, we will parse this out on the server side to be used + //for persisting. + var macroSyntaxComment = ""; + //create an id class for this element so we can re-select it after inserting + var uniqueId = "umb-macro-" + editor.dom.uniqueId(); + var macroDiv = editor.dom.create('div', { + 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId + }, + macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); + + editor.selection.setNode(macroDiv); + + var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); + + //async load the macro content + this.loadMacroContent($macroDiv, macroObject, $scope); + + }, + + /** loads in the macro content async from the server */ + loadMacroContent: function ($macroDiv, macroData, $scope) { + + //if we don't have the macroData, then we'll need to parse it from the macro div + if (!macroData) { + var contents = $macroDiv.contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + macroData = parsed; + } + + var $ins = $macroDiv.find("ins"); + + //show the throbber + $macroDiv.addClass("loading"); + + var contentId = $routeParams.id; + + //need to wrap in safe apply since this might be occuring outside of angular + angularHelper.safeApply($scope, function () { + macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) + .then(function (htmlResult) { + + $macroDiv.removeClass("loading"); + htmlResult = htmlResult.trim(); + if (htmlResult !== "") { + $ins.html(htmlResult); + } + }); + }); + + }, + + createLinkPicker: function (editor, $scope, onClick) { + + function createLinkList(callback) { + return function () { + var linkList = editor.settings.link_list; + + if (typeof (linkList) === "string") { + tinymce.util.XHR.send({ + url: linkList, + success: function (text) { + callback(tinymce.util.JSON.parse(text)); + } + }); + } else { + callback(linkList); + } + }; + } + + function showDialog(linkList) { + var data = {}, + selection = editor.selection, + dom = editor.dom, + selectedElm, anchorElm, initialText; + var win, linkListCtrl, relListCtrl, targetListCtrl; + + function linkListChangeHandler(e) { + var textCtrl = win.find('#text'); + + if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { + textCtrl.value(e.control.text()); + } + + win.find('#href').value(e.control.value()); + } + + function buildLinkList() { + var linkListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(linkList, function (link) { + linkListItems.push({ + text: link.text || link.title, + value: link.value || link.url, + menu: link.menu + }); + }); + + return linkListItems; + } + + function buildRelList(relValue) { + var relListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(editor.settings.rel_list, function (rel) { + relListItems.push({ + text: rel.text || rel.title, + value: rel.value, + selected: relValue === rel.value + }); + }); + + return relListItems; + } + + function buildTargetList(targetValue) { + var targetListItems = [{ + text: 'None', + value: '' + }]; + + if (!editor.settings.target_list) { + targetListItems.push({ + text: 'New window', + value: '_blank' + }); + } + + tinymce.each(editor.settings.target_list, function (target) { + targetListItems.push({ + text: target.text || target.title, + value: target.value, + selected: targetValue === target.value + }); + }); + + return targetListItems; + } + + function buildAnchorListControl(url) { + var anchorList = []; + + tinymce.each(editor.dom.select('a:not([href])'), function (anchor) { + var id = anchor.name || anchor.id; + + if (id) { + anchorList.push({ + text: id, + value: '#' + id, + selected: url.indexOf('#' + id) !== -1 + }); + } + }); + + if (anchorList.length) { + anchorList.unshift({ + text: 'None', + value: '' + }); + + return { + name: 'anchor', + type: 'listbox', + label: 'Anchors', + values: anchorList, + onselect: linkListChangeHandler + }; + } + } + + function updateText() { + if (!initialText && data.text.length === 0) { + this.parent().parent().find('#text')[0].value(this.value()); + } + } + + selectedElm = selection.getNode(); + anchorElm = dom.getParent(selectedElm, 'a[href]'); + + data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({ + format: 'text' + }); + data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; + data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; + data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; + + if (selectedElm.nodeName === "IMG") { + data.text = initialText = " "; + } + + if (linkList) { + linkListCtrl = { + type: 'listbox', + label: 'Link list', + values: buildLinkList(), + onselect: linkListChangeHandler + }; + } + + if (editor.settings.target_list !== false) { + targetListCtrl = { + name: 'target', + type: 'listbox', + label: 'Target', + values: buildTargetList(data.target) + }; + } + + if (editor.settings.rel_list) { + relListCtrl = { + name: 'rel', + type: 'listbox', + label: 'Rel', + values: buildRelList(data.rel) + }; + } + + var currentTarget = null; + + //if we already have a link selected, we want to pass that data over to the dialog + if (anchorElm) { + var anchor = $(anchorElm); + currentTarget = { + name: anchor.attr("title"), + url: anchor.attr("href"), + target: anchor.attr("target") + }; + + // drop the lead char from the anchor text, if it has a value + var anchorVal = anchor[0].dataset.anchor; + if (anchorVal) { + currentTarget.anchor = anchorVal.substring(1); + } + + //locallink detection, we do this here, to avoid poluting the dialogservice + //so the dialog service can just expect to get a node-like structure + if (currentTarget.url.indexOf("localLink:") > 0) { + // if the current link has an anchor, it needs to be considered when getting the udi/id + // if an anchor exists, reduce the substring max by its length plus two to offset the removed prefix and trailing curly brace + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.lastIndexOf("}")); + + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } else { + currentTarget.id = linkId; + } + } + } + + if (onClick) { + onClick(currentTarget, anchorElm); + } + + } + + editor.addButton('link', { + icon: 'link', + tooltip: 'Insert/edit link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]' + }); + + editor.addButton('unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink', + stateSelector: 'a[href]' + }); + + editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); + this.showDialog = showDialog; + + editor.addMenuItem('link', { + icon: 'link', + text: 'Insert link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + + }, + + insertLinkInEditor: function (editor, target, anchorElm) { + + var href = target.url; + // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null + var hasUdi = target.udi ? true : false; + var id = hasUdi ? target.udi : (target.id ? target.id : null); + + // if an anchor exists, check that it is appropriately prefixed + if (target.anchor && target.anchor[0] !== '?' && target.anchor[0] !== '#') { + target.anchor = (target.anchor.indexOf('=') === -1 ? '#' : '?') + target.anchor; + } + + // the href might be an external url, so check the value for an anchor/qs + // href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor + if (!target.anchor) { + var urlParts = href.split(/(#|\?)/); + if (urlParts.length === 3) { + href = urlParts[0]; + target.anchor = urlParts[1] + urlParts[2]; + } + } + + //Create a json obj used to create the attributes for the tag + function createElemAttributes() { + var a = { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null + }; + + if (hasUdi) { + a["data-udi"] = target.udi; + } else if (target.id) { + a["data-id"] = target.id; + } + + if (target.anchor) { + a["data-anchor"] = target.anchor; + a.href = a.href + target.anchor; + } else { + a["data-anchor"] = null; + } + + return a; + } + + function insertLink() { + if (anchorElm) { + editor.dom.setAttribs(anchorElm, createElemAttributes()); + + editor.selection.select(anchorElm); + editor.execCommand('mceEndTyping'); + } else { + editor.execCommand('mceInsertLink', false, createElemAttributes()); + } + } + + if (!href && !target.anchor) { + editor.execCommand('unlink'); + return; + } + + //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set + if (id && (angular.isUndefined(target.isMedia) || !target.isMedia)) { + + href = "/{localLink:" + id + "}"; + + insertLink(); + return; + } + + if (!href) { + href = ""; + } + + // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified + if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) { + // assume it's a mailto link + href = 'mailto:' + href; + insertLink(); + return; + } + + // Is www. prefixed + if (/^\s*www\./i.test(href)) { + href = 'http://' + href; + insertLink(); + return; + } + + insertLink(); + + } + + }; +} + +angular.module('umbraco.services').factory('tinyMceService', tinyMceService); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 67d62de223..b1fa038c44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + function ($scope, eventsService, dialogService, entityResource, mediaHelper, userService, localizationService, tinyMceService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -40,10 +40,10 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", } // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); + entityResource.getUrlAndAnchors(id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.target.url = resp.url; + }); } else if ($scope.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? @@ -88,10 +88,10 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", if (args.node.id < 0) { $scope.target.url = "/"; } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); + entityResource.getUrlAndAnchors(args.node.id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.target.url = resp.url; + }); } if (!angular.isUndefined($scope.target.isMedia)) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 8a1c3eb9a9..787fe4186e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + function ($scope, eventsService, dialogService, entityResource, mediaHelper, userService, localizationService, tinyMceService) { var dialogOptions = $scope.model; var searchText = "Search..."; @@ -45,10 +45,10 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", }); }); - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + + entityResource.getUrlAndAnchors(id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } } else if ($scope.model.target.url.length) { @@ -88,9 +88,9 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - contentResource.getById(args.node.id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + entityResource.getUrlAndAnchors(args.node.id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index e3e5d5e072..30ea1eaef6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -12,19 +12,22 @@ function openLinkPicker(editor, currentTarget, anchorElement) { - vm.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), - dataTypeId: $scope.model.dataTypeId, - ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - vm.linkPickerOverlay.show = false; - vm.linkPickerOverlay = null; - } - }; + entityResource.getAnchors($scope.model.value).then(function(anchorValues) { + vm.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: anchorValues, + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + vm.linkPickerOverlay.show = false; + vm.linkPickerOverlay = null; + } + }; + }); + } function openMediaPicker(editor, currentTarget, userData) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index cadbc5f57a..dd9fb404c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState, entityResource) { $scope.isLoading = true; @@ -270,19 +270,25 @@ angular.module("umbraco") }); tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - dataTypeId: $scope.model.dataTypeId, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; + + entityResource.getAnchors($scope.model.value).then(function(anchorValues){ + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: anchorValues, + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + + + }); //Create the insert media plugin diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 36f4465e96..d6e997b57e 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -289,6 +289,26 @@ namespace Umbraco.Web.Editors publishedContentExists: i => Umbraco.TypedContent(i) != null); } + + [HttpGet] + public UrlAndAnchors GetUrlAndAnchors(int id) + { + var x = GetResultForId(id, UmbracoEntityTypes.Document); + + var url = Umbraco.Url(id); + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); + return new UrlAndAnchors(url, anchorValues); + } + + [HttpPost] + public IList GetAnchors(string rteContent) + { + + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); + return anchorValues; + } + + #region GetById /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs new file mode 100644 index 0000000000..a8cf2e26ee --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "urlAndAnchors", Namespace = "")] + public class UrlAndAnchors + { + public UrlAndAnchors(string url, IList anchorValues) + { + Url = url; + AnchorValues = anchorValues; + } + + [DataMember(Name = "url")] + public string Url { get; } + + [DataMember(Name = "anchorValues")] + public IList AnchorValues { get; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 90624df5f2..d4fd27c323 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,2027 +1,2028 @@ - - - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - + + + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file From 966b07ba1c969934b322b0266a6a9e0e1abda015 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 21:12:38 +0200 Subject: [PATCH 13/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Changed entity service to only return small model with media path. And updated media picker to use entity service when loading data. --- .../Repositories/EntityRepository.cs | 1630 ++++++++--------- src/Umbraco.Core/Services/EntityService.cs | 1536 ++++++++-------- src/Umbraco.Core/Services/IEntityService.cs | 578 +++--- src/Umbraco.Core/Services/IRelationService.cs | 604 +++--- src/Umbraco.Core/Services/RelationService.cs | 1488 ++++++++------- .../common/services/mediahelper.service.js | 76 +- .../common/dialogs/mediapicker.controller.js | 274 +-- .../mediaPicker/mediapicker.controller.js | 13 +- .../mediapicker/mediapicker.controller.js | 2 +- .../Models/Mapping/PreValueDisplayResolver.cs | 1 + .../FilterAllowedOutgoingMediaAttribute.cs | 2 +- src/umbraco.cms/businesslogic/web/Access.cs | 1162 ++++++------ 12 files changed, 3569 insertions(+), 3797 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index f28b226932..495c0109ad 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,891 +1,747 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents the EntityRepository used to query objects. - /// - /// - /// This is limited to objects that are based in the umbracoNode-table. - /// - internal class EntityRepository : DisposableObjectSlim, IEntityRepository - { - private readonly IDatabaseUnitOfWork _work; - - public EntityRepository(IDatabaseUnitOfWork work) - { - _work = work; - } - - /// - /// Returns the Unit of Work added to the repository - /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } - - /// - /// Internal for testing purposes - /// - internal Guid UnitKey - { - get { return (Guid)_work.Key; } - } - - #region Query Methods - - public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, IQuery filter = null) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var factory = new UmbracoEntityFactory(); - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents the EntityRepository used to query objects. + /// + /// + /// This is limited to objects that are based in the umbracoNode-table. + /// + internal class EntityRepository : DisposableObjectSlim, IEntityRepository + { + private readonly IDatabaseUnitOfWork _work; + + public EntityRepository(IDatabaseUnitOfWork work) + { + _work = work; + } + + /// + /// Returns the Unit of Work added to the repository + /// + protected internal IDatabaseUnitOfWork UnitOfWork + { + get { return _work; } + } + + /// + /// Internal for testing purposes + /// + internal Guid UnitKey + { + get { return (Guid)_work.Key; } + } + + #region Query Methods + + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, IQuery filter = null) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + var factory = new UmbracoEntityFactory(); + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + { + sql.Where(filterClause.Item1, filterClause.Item2); + } + } + }, objectTypeId); + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)); pagedSql = (orderDirection == Direction.Descending) ? pagedSql.OrderByDescending("umbracoNode.id") : pagedSql.OrderBy("umbracoNode.id"); - - IEnumerable result; - - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) + + IEnumerable result; + + if (isMedia) + { + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + + var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); + var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + + //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before + foreach (var idGroup in ids) + { + var propSql = GetPropertySql(Constants.ObjectTypes.Media) .Where("contentNodeId IN (@ids)", new { ids = idGroup }); propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } - - //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will - //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. - - //generate a query that does not contain the LEFT Join for parent, this would cause - //the COUNT(*) query to return the wrong - var sqlCountClause = GetBaseWhere( - (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query - isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translatorCount = new SqlTranslator(sqlCountClause, query); - var countSql = translatorCount.Translate(); - - totalRecords = _work.Database.ExecuteScalar(countSql); - - return result; - } - - public IUmbracoEntity GetByKey(Guid key) - { - var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - - - } - - public virtual IUmbracoEntity Get(int id) - { - var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public virtual IUmbracoEntity Get(int id, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) - : PerformGetAll(objectTypeId); - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAll(objectTypeId); - } - - private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) - { - var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - return entities; - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) - : PerformGetAllPaths(objectTypeId); - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAllPaths(objectTypeId); - } - - private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) - { - var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); - if (filter != null) filter(sql); - return _work.Database.Fetch(sql); - } - - public virtual IEnumerable GetByQuery(IQuery query) - { - var sqlClause = GetBase(false, false, null); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy(false, false)); - - var dtos = _work.Database.Fetch(sql); - - var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - return list; - } - - /// - /// Gets entities by query. - /// - /// - /// - /// - /// - /// Note that this will also fetch all property data for media items, which can cause performance problems - /// when used without paging, in sites with large amounts of data in cmsPropertyData. - /// - public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) - { - var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - var wheres = query.GetWhereClauses().ToArray(); - - var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql => - { - //adds the additional filters - foreach (var whereClause in wheres) - { - sql.Where(whereClause.Item1, whereClause.Item2); - } - }); - - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, mediaSql); - return entities; - } - else - { - return GetByQueryInternal(entitySql, isContent, isMedia); - } - } - - /// - /// Gets entities by query without fetching property data. - /// - /// - /// - /// - /// - /// This is supposed to be internal and can be used when getting all entities without paging, without causing - /// performance issues. - /// - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - return GetByQueryInternal(entitySql, false, true); - } - - internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) - { - var factory = new UmbracoEntityFactory(); - - //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData - var finalSql = entitySql.Append(GetGroupBy(isContent, isMedia)); - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - - #endregion - - - #region Sql Statements - - protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); - } - - protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); - } - - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) - { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId); - - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); - } - - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .InnerJoin() - .On(dto => dto.Id, dto => dto.PropertyTypeId) - .InnerJoin() - .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); - - return sql; - } - - private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) - { - //this will add any dataNvarchar property to the output which can be added to the additional properties - - var joinSql = GetPropertySql(Constants.ObjectTypes.Media); - - if (filter != null) - { - filter(joinSql); - } - - //We're going to create a query to query against the entity SQL - // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join - // the entitySql query, we have to join the wrapped query to get the ntext in the result - - var wrappedSql = new Sql("SELECT * FROM (") - .Append(entitySql) - .Append(new Sql(") tmpTbl LEFT JOIN (")) - .Append(joinSql) - .Append(new Sql(") as property ON id = property.contentNodeId")) - .OrderBy("sortOrder, id"); - - return wrappedSql; - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) - { - return GetBase(isContent, isMedia, customFilter, false); - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) - { - var columns = new List(); - if (isCount) - { - columns.Add("COUNT(*)"); - } - else - { - columns.AddRange(new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate", - "COUNT(parent.parentID) as children" - }); - - if (isContent || isMedia) - { - if (isContent) - { - //only content has/needs this info - columns.Add("published.versionId as publishedVersion"); - columns.Add("document.versionId as newestVersion"); - columns.Add("contentversion.id as versionId"); - } - - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - } - - //Creates an SQL query to return a single row for the entity - - var entitySql = new Sql() - .Select(columns.ToArray()) - .From("umbracoNode umbracoNode"); - - if (isContent || isMedia) - { - entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); - - if (isContent) - { - //only content has/needs this info - entitySql - .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") - .InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId") - .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") - .On("umbracoNode.id = published.nodeId"); - } - - entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); - } - - if (isCount == false) - { - entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); - } - - if (customFilter != null) - { - customFilter(entitySql); - } - - return entitySql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType) - { - var sql = baseQuery(isContent, isMedia, filter) - .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.id = @Id", new { Id = id }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.uniqueID = @UniqueID", new {UniqueID = key}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", - new {Id = id, NodeObjectType = nodeObjectType}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) - { - var sql = baseQuery(isContent, isMedia, null) - .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", - new { UniqueID = key, NodeObjectType = nodeObjectType }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) - { - var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate" - }; - - if (isContent || isMedia) - { - if (isContent) - { - columns.Add("published.versionId"); - columns.Add("document.versionId"); - columns.Add("contentversion.id"); - } - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - - var sql = new Sql() - .GroupBy(columns.ToArray()); - - if (includeSort) - { - sql = sql.OrderBy("umbracoNode.sortOrder"); - } - - return sql; - } - - #endregion - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - UnitOfWork.DisposeIfDisposable(); - } - - public bool Exists(Guid key) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; - } - - public bool Exists(int id) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; - } - - #region private classes - - [ExplicitColumns] - internal class UmbracoPropertyDto - { - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("propertyTypeAlias")] - public string PropertyAlias { get; set; } - - [Column("dataNvarchar")] - public string NVarcharValue { get; set; } - - [Column("dataNtext")] - public string NTextValue { get; set; } - } - - /// - /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts - /// a dynamic instance. - /// - /// - /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones - /// defined on the entity so we can them to additional data - /// - internal class UmbracoEntityRelator - { - internal UmbracoEntity Current; - private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - - internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same UmbracoEntity as the current one we're processing - if (Current != null && Current.Key == a.uniqueID) - { - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) - { - // Add this UmbracoProperty to the current additional data - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; - } - - // Return null to indicate we're not done with this UmbracoEntity yet - return null; - } - - // This is a different UmbracoEntity to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current UmbracoEntityDto - var prev = Current; - - // Setup the new current UmbracoEntity - - Current = _factory.BuildEntityFromDynamic(a); - - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) - { - //add the property/create the prop list if null - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; - } - - // Return the now populated previous UmbracoEntity (or null if first time through) - return prev; - } - } - - private class EntityDefinitionCollection : KeyedCollection - { - protected override int GetKeyForItem(EntityDefinition item) - { - return item.Id; - } - - /// - /// if this key already exists if it does then we need to check - /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one - /// - /// - /// - public bool AddOrUpdate(EntityDefinition item) - { - if (Dictionary == null) - { - base.Add(item); - return true; - } - - var key = GetKeyForItem(item); - EntityDefinition found; - if (TryGetValue(key, out found)) - { - //it already exists and it's older so we need to replace it - if (item.VersionId > found.VersionId) - { - var currIndex = Items.IndexOf(found); - if (currIndex == -1) - throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); - - //replace the current one with the newer one - SetItem(currIndex, item); - return true; - } - //could not add or update - return false; - } - - base.Add(item); - return true; - } - - private bool TryGetValue(int key, out EntityDefinition val) - { - if (Dictionary == null) - { - val = null; - return false; - } - return Dictionary.TryGetValue(key, out val); - } - } - - private class EntityDefinition - { - private readonly UmbracoEntityFactory _factory; - private readonly dynamic _entity; - private readonly bool _isContent; - private readonly bool _isMedia; - - public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia) - { - _factory = factory; - _entity = entity; - _isContent = isContent; - _isMedia = isMedia; - } - - public IUmbracoEntity BuildFromDynamic() - { - return _factory.BuildEntityFromDynamic(_entity); - } - - public int Id - { - get { return _entity.id; } - } - - public int VersionId - { - get - { - if (_isContent || _isMedia) - { - return _entity.versionId; - } - return _entity.id; - } - } - } - #endregion - } -} + + //This does NOT fetch all data into memory in a list, this will read + // over the records as a data reader, this is much better for performance and memory, + // but it means that during the reading of this data set, nothing else can be read + // from SQL server otherwise we'll get an exception. + var allPropertyData = _work.Database.Query(propSql); + + //keep track of the current property data item being enumerated + var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); + var hasCurrent = false; // initially there is no enumerator.Current + + try + { + //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, + // which allows us to more efficiently iterate over the large data set of property values. + foreach (var entity in entities) + { + // assemble the dtos for this def + // use the available enumerator.Current if any else move to next + while (hasCurrent || propertyDataSetEnumerator.MoveNext()) + { + if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) + { + hasCurrent = false; // enumerator.Current is not available + + //the property data goes into the additional data + entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty + { + PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, + Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) + ? propertyDataSetEnumerator.Current.dataNvarchar + : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) + }; + } + else + { + hasCurrent = true; // enumerator.Current is available for another def + break; // no more propertyDataDto for this def + } + } + } + } + finally + { + propertyDataSetEnumerator.Dispose(); + } + } + + result = entities; + } + else + { + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + } + + //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will + //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. + + //generate a query that does not contain the LEFT Join for parent, this would cause + //the COUNT(*) query to return the wrong + var sqlCountClause = GetBaseWhere( + (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query + isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + { + sql.Where(filterClause.Item1, filterClause.Item2); + } + } + }, objectTypeId); + var translatorCount = new SqlTranslator(sqlCountClause, query); + var countSql = translatorCount.Translate(); + + totalRecords = _work.Database.ExecuteScalar(countSql); + + return result; + } + + public IUmbracoEntity GetByKey(Guid key) + { + var sql = GetBaseWhere(GetBase, false, false, key); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); + } + var found = collection.FirstOrDefault(); + return found != null ? found.BuildFromDynamic() : null; + + + } + + public virtual IUmbracoEntity Get(int id) + { + var sql = GetBaseWhere(GetBase, false, false, id); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public virtual IUmbracoEntity Get(int id, Guid objectTypeId) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); + } + var found = collection.FirstOrDefault(); + return found != null ? found.BuildFromDynamic() : null; + + } + + public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) + { + return ids.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) + : PerformGetAll(objectTypeId); + } + + public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) + { + return keys.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) + : PerformGetAll(objectTypeId); + } + + private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) + { + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); + } + return collection.Select(x => x.BuildFromDynamic()).ToList(); + } + + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) + { + return ids.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) + : PerformGetAllPaths(objectTypeId); + } + + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) + { + return keys.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) + : PerformGetAllPaths(objectTypeId); + } + + private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) + { + var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); + if (filter != null) filter(sql); + return _work.Database.Fetch(sql); + } + + public virtual IEnumerable GetByQuery(IQuery query) + { + var sqlClause = GetBase(false, false, null); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate().Append(GetGroupBy(false, false)); + + var dtos = _work.Database.Fetch(sql); + + var factory = new UmbracoEntityFactory(); + var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + + return list; + } + + /// + /// Gets entities by query. + /// + /// + /// + /// + /// + /// Note that this will also fetch all property data for media items, which can cause performance problems + /// when used without paging, in sites with large amounts of data in cmsPropertyData. + /// + public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) + { + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); + + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); + + return GetByQueryInternal(entitySql, isContent, isMedia); + } + + /// + /// Gets entities by query without fetching property data. + /// + /// + /// + /// + /// + /// This is supposed to be internal and can be used when getting all entities without paging, without causing + /// performance issues. + /// + internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) + { + var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); + + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); + + return GetByQueryInternal(entitySql, false, true); + } + + internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) + { + var factory = new UmbracoEntityFactory(); + + //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData + var finalSql = entitySql.Append(GetGroupBy(isContent, isMedia)); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(finalSql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); + } + return collection.Select(x => x.BuildFromDynamic()).ToList(); + } + + #endregion + + + #region Sql Statements + + protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return entitySql.Append(GetGroupBy(isContent, true, false)); + } + + protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return entitySql.Append(GetGroupBy(isContent, true, false)); + } + + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return entitySql.Append(GetGroupBy(isContent, true, false)); + } + + private Sql GetPropertySql(string nodeObjectType) + { + var sql = new Sql() + .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .InnerJoin() + .On(dto => dto.Id, dto => dto.PropertyTypeId) + .InnerJoin() + .On(dto => dto.DataTypeId, dto => dto.DataTypeId) + .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); + + return sql; + } + + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) + { + return GetBase(isContent, isMedia, customFilter, false); + } + + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) + { + var columns = new List(); + if (isCount) + { + columns.Add("COUNT(*)"); + } + else + { + columns.AddRange(new List + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate", + "COUNT(parent.parentID) as children" + }); + + if (isContent || isMedia) + { + if (isContent) + { + //only content has/needs this info + columns.Add("published.versionId as publishedVersion"); + columns.Add("document.versionId as newestVersion"); + columns.Add("contentversion.id as versionId"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); + } + + if (isMedia) + { + columns.Add("media.mediaPath"); + } + } + + //Creates an SQL query to return a single row for the entity + + var entitySql = new Sql() + .Select(columns.ToArray()) + .From("umbracoNode umbracoNode"); + + if (isContent || isMedia) + { + entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); + + if (isContent) + { + //only content has/needs this info + entitySql + .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") + .InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId") + .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") + .On("umbracoNode.id = published.nodeId"); + } + + if (isMedia) + { + entitySql.InnerJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); + } + + entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); + } + + + + if (isCount == false) + { + entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + } + + if (customFilter != null) + { + customFilter(entitySql); + } + + return entitySql; + } + + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType) + { + var sql = baseQuery(isContent, isMedia, filter) + .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) + { + var sql = baseQuery(isContent, isMedia, null) + .Where("umbracoNode.id = @Id", new { Id = id }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + + return sql; + } + + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) + { + var sql = baseQuery(isContent, isMedia, null) + .Where("umbracoNode.uniqueID = @UniqueID", new {UniqueID = key}); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + + return sql; + } + + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) + { + var sql = baseQuery(isContent, isMedia, null) + .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", + new {Id = id, NodeObjectType = nodeObjectType}); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) + { + var sql = baseQuery(isContent, isMedia, null) + .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", + new { UniqueID = key, NodeObjectType = nodeObjectType }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) + { + var columns = new List + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate" + }; + + if (isContent || isMedia) + { + if (isContent) + { + columns.Add("published.versionId"); + columns.Add("document.versionId"); + columns.Add("contentversion.id"); + } + + if (isMedia) + { + columns.Add("media.mediaPath"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); + } + + var sql = new Sql() + .GroupBy(columns.ToArray()); + + if (includeSort) + { + sql = sql.OrderBy("umbracoNode.sortOrder"); + } + + return sql; + } + + #endregion + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + UnitOfWork.DisposeIfDisposable(); + } + + public bool Exists(Guid key) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); + return _work.Database.ExecuteScalar(sql) > 0; + } + + public bool Exists(int id) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); + return _work.Database.ExecuteScalar(sql) > 0; + } + + #region private classes + + + private class EntityDefinitionCollection : KeyedCollection + { + protected override int GetKeyForItem(EntityDefinition item) + { + return item.Id; + } + + /// + /// if this key already exists if it does then we need to check + /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one + /// + /// + /// + public bool AddOrUpdate(EntityDefinition item) + { + if (Dictionary == null) + { + base.Add(item); + return true; + } + + var key = GetKeyForItem(item); + EntityDefinition found; + if (TryGetValue(key, out found)) + { + //it already exists and it's older so we need to replace it + if (item.VersionId > found.VersionId) + { + var currIndex = Items.IndexOf(found); + if (currIndex == -1) + throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); + + //replace the current one with the newer one + SetItem(currIndex, item); + return true; + } + //could not add or update + return false; + } + + base.Add(item); + return true; + } + + private bool TryGetValue(int key, out EntityDefinition val) + { + if (Dictionary == null) + { + val = null; + return false; + } + return Dictionary.TryGetValue(key, out val); + } + } + + private class EntityDefinition + { + private readonly UmbracoEntityFactory _factory; + private readonly dynamic _entity; + private readonly bool _isContent; + private readonly bool _isMedia; + + public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia) + { + _factory = factory; + _entity = entity; + _isContent = isContent; + _isMedia = isMedia; + } + + public IUmbracoEntity BuildFromDynamic() + { + return _factory.BuildEntityFromDynamic(_entity); + } + + public int Id + { + get { return _entity.id; } + } + + public int VersionId + { + get + { + if (_isContent || _isMedia) + { + return _entity.versionId; + } + return _entity.id; + } + } + } + #endregion + } +} diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 1e44bec909..906ece1081 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -1,802 +1,742 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Services -{ - public class EntityService : ScopeRepositoryService, IEntityService - { - private readonly Dictionary>> _supportedObjectTypes; - private readonly IdkMap _idkMap; - - public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - _idkMap = idkMap; - - _supportedObjectTypes = new Dictionary>> - { - {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, - {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, - {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.GetContentType)}, - {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, - {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, contentTypeService.GetMediaType)}, - {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, - {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, - }; - } - - #region Static Queries - - private IQuery _rootEntityQuery; - - #endregion - - internal IdkMap IdkMap { get { return _idkMap; } } - - /// - /// Returns the integer id for a given GUID - /// - /// - /// - /// - public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) - { - return _idkMap.GetIdForKey(key, umbracoObjectType); - } - - public Attempt GetIdForUdi(Udi udi) - { - return _idkMap.GetIdForUdi(udi); - } - - /// - /// Returns the GUID for a given integer id - /// - /// - /// - /// - public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) - { - return _idkMap.GetKeyForId(id, umbracoObjectType); - } - - public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetByKey(key); - uow.Commit(); - return ret; - } - } - - //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. - throw new NotSupportedException(); - - //var objectType = GetObjectType(key); - //var entityType = GetEntityType(objectType); - //var typeFullName = entityType.FullName; - //var entity = _supportedObjectTypes[typeFullName].Item2(id); - - //return entity; - } - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id); - return ret; - } - } - - var objectType = GetObjectType(id); - var entityType = GetEntityType(objectType); - var typeFullName = entityType.FullName; - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) - { - if (loadBaseType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetByKey(key, objectTypeId); - return ret; - } - } - - //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. - throw new NotSupportedException(); - - //var entityType = GetEntityType(umbracoObjectType); - //var typeFullName = entityType.FullName; - //var entity = _supportedObjectTypes[typeFullName].Item2(id); - - //return entity; - } - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) - { - if (loadBaseType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id, objectTypeId); - return ret; - } - } - - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity - { - throw new NotImplementedException(); - } - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id); - return ret; - } - } - - var typeFullName = typeof(T).FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - /// - /// Gets the parent of entity by its id - /// - /// Id of the entity to retrieve the Parent for - /// An - public virtual IUmbracoEntity GetParent(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - - if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) - return null; - var ret = repository.Get(entity.ParentId); - return ret; - } - } - - /// - /// Gets the parent of entity by its id and UmbracoObjectType - /// - /// Id of the entity to retrieve the Parent for - /// UmbracoObjectType of the parent to retrieve - /// An - public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - - if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) - return null; - var objectTypeId = umbracoObjectType.GetGuid(); - - var ret = repository.Get(entity.ParentId, objectTypeId); - return ret; - } - } - - /// - /// Gets a collection of children by the parents Id - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - public virtual IEnumerable GetChildren(int parentId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - var contents = repository.GetByQuery(query); - return contents; - } - } - - /// - /// Gets a collection of children by the parents Id and UmbracoObjectType - /// - /// Id of the parent to retrieve children for - /// UmbracoObjectType of the children to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - return repository.GetByQuery(query, objectTypeId); - } - } - - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); - } - } - - /// - /// Returns a paged collection of children - /// - /// The parent id to return children for - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Trashed == false); - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Returns a paged collection of descendants - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - //if the id is System Root, then just get all - if (id != Constants.System.Root) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class EntityService : ScopeRepositoryService, IEntityService + { + private readonly Dictionary>> _supportedObjectTypes; + private readonly IdkMap _idkMap; + + public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, + IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, + IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + _idkMap = idkMap; + + _supportedObjectTypes = new Dictionary>> + { + {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, + {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, + {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.GetContentType)}, + {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, + {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, contentTypeService.GetMediaType)}, + {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, + {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, + }; + } + + #region Static Queries + + private IQuery _rootEntityQuery; + + #endregion + + internal IdkMap IdkMap { get { return _idkMap; } } + + /// + /// Returns the integer id for a given GUID + /// + /// + /// + /// + public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) + { + return _idkMap.GetIdForKey(key, umbracoObjectType); + } + + public Attempt GetIdForUdi(Udi udi) + { + return _idkMap.GetIdForUdi(udi); + } + + /// + /// Returns the GUID for a given integer id + /// + /// + /// + /// + public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) + { + return _idkMap.GetKeyForId(id, umbracoObjectType); + } + + public IUmbracoEntity GetByKey(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key); + uow.Commit(); + return ret; + } + } + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// An + public virtual IUmbracoEntity Get(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + return ret; + } + } + + public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key, objectTypeId); + return ret; + } + } + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + // An + public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id, objectTypeId); + return ret; + } + } + + public IUmbracoEntity GetByKey(Guid key) where T : IUmbracoEntity + { + throw new NotImplementedException(); + } + + /// + /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Type of the model to retrieve. Must be based on an + /// Id of the object to retrieve + /// An + public virtual IUmbracoEntity Get(int id) where T : IUmbracoEntity + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + return ret; + } + } + + /// + /// Gets the parent of entity by its id + /// + /// Id of the entity to retrieve the Parent for + /// An + public virtual IUmbracoEntity GetParent(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) + return null; + var ret = repository.Get(entity.ParentId); + return ret; + } + } + + /// + /// Gets the parent of entity by its id and UmbracoObjectType + /// + /// Id of the entity to retrieve the Parent for + /// UmbracoObjectType of the parent to retrieve + /// An + public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) + return null; + var objectTypeId = umbracoObjectType.GetGuid(); + + var ret = repository.Get(entity.ParentId, objectTypeId); + return ret; + } + } + + /// + /// Gets a collection of children by the parents Id + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + public virtual IEnumerable GetChildren(int parentId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + + var contents = repository.GetByQuery(query); + return contents; + } + } + + /// + /// Gets a collection of children by the parents Id and UmbracoObjectType + /// + /// Id of the parent to retrieve children for + /// UmbracoObjectType of the children to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + return repository.GetByQuery(query, objectTypeId); + } + } + + /// + /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) + { + var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + + // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we + // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. + // See this issue for details on why we need this: + // https://github.com/umbraco/Umbraco-CMS/issues/3457 + return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); + } + } + + /// + /// Returns a paged collection of children + /// + /// The parent id to return children for + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Trashed == false); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) { - //lookup the path so we can use it in the prefix query below - var itemPaths = repository.GetAllPaths(objectTypeId, id).ToArray(); - if (itemPaths.Length == 0) - { - totalRecords = 0; - return Enumerable.Empty(); - } + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Returns a paged collection of descendants + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + //lookup the path so we can use it in the prefix query below + var itemPaths = repository.GetAllPaths(objectTypeId, id).ToArray(); + if (itemPaths.Length == 0) + { + totalRecords = 0; + return Enumerable.Empty(); + } var itemPath = itemPaths[0].Path; - - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", itemPath), TextColumnType.NVarchar)); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - /// - /// Returns a paged collection of descendants. - /// - public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - totalRecords = 0; - - var idsA = ids.ToArray(); - if (idsA.Length == 0) - return Enumerable.Empty(); - - var objectTypeId = umbracoObjectType.GetGuid(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - if (idsA.All(x => x != Constants.System.Root)) - { - //lookup the paths so we can use it in the prefix query below - var itemPaths = repository.GetAllPaths(objectTypeId, idsA).ToArray(); - if (itemPaths.Length == 0) - { - totalRecords = 0; - return Enumerable.Empty(); - } - - var clauses = new List>>(); - foreach (var id in idsA) + + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", itemPath), TextColumnType.NVarchar)); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + /// + /// Returns a paged collection of descendants. + /// + public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + totalRecords = 0; + + var idsA = ids.ToArray(); + if (idsA.Length == 0) + return Enumerable.Empty(); + + var objectTypeId = umbracoObjectType.GetGuid(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + if (idsA.All(x => x != Constants.System.Root)) + { + //lookup the paths so we can use it in the prefix query below + var itemPaths = repository.GetAllPaths(objectTypeId, idsA).ToArray(); + if (itemPaths.Length == 0) + { + totalRecords = 0; + return Enumerable.Empty(); + } + + var clauses = new List>>(); + foreach (var id in idsA) { //if the id is root then don't add any clauses - if (id != Constants.System.Root) - { - var itemPath = itemPaths.FirstOrDefault(x => x.Id == id); - if (itemPath == null) continue; - var path = itemPath.Path; - var qid = id; - clauses.Add(x => x.Path.SqlStartsWith(string.Format("{0},", path), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); - } - } - query.WhereAny(clauses); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Returns a paged collection of descendants from the root - /// - /// - /// - /// - /// - /// - /// - /// - /// true/false to include trashed objects - /// - public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - //don't include trashed if specfied - if (includeTrashed == false) - { - query.Where(x => x.Trashed == false); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// An enumerable list of objects - public virtual IEnumerable GetDescendents(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - var pathMatch = entity.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); - - var entities = repository.GetByQuery(query); - return entities; - } - } - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// UmbracoObjectType of the descendents to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); - - var entities = repository.GetByQuery(query, objectTypeId); - return entities; - } - } - - /// - /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. - /// - /// UmbracoObjectType of the root entities to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) - { - //create it once if it is needed (no need for locking here) - if (_rootEntityQuery == null) - { - _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); - } - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); - return entities; - } - } - - /// - /// Gets a collection of all of a given type. - /// - /// Type of the entities to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity - { - var typeFullName = typeof(T).FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - var objectType = _supportedObjectTypes[typeFullName].Item1; - - return GetAll(objectType, ids); - } - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, ids); - return ret; - } - } - - public IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, keys); - return ret; - } - } - - public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - return repository.GetAllPaths(objectTypeId, ids); - } - } - - public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - return repository.GetAllPaths(objectTypeId, keys); - } - } - - /// - /// Gets a collection of - /// - /// Guid id of the UmbracoObjectType - /// - /// An enumerable list of objects - public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) - { - var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, ids); - return ret; - } - } - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Id of the entity - /// - public virtual UmbracoObjectTypes GetObjectType(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); - var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); - var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - } - } - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Unique Id of the entity - /// - public virtual UmbracoObjectTypes GetObjectType(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql().Select("nodeObjectType").From().Where(x => x.UniqueId == key); - var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); - var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - } - } - - /// - /// Gets the UmbracoObjectType from an IUmbracoEntity. - /// - /// - /// - public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) - { - var entityImpl = entity as UmbracoEntity; - if (entityImpl == null) - return GetObjectType(entity.Id); - - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); - } - - /// - /// Gets the Type of an entity by its Id - /// - /// Id of the entity - /// Type of the entity - public virtual Type GetEntityType(int id) - { - var objectType = GetObjectType(id); - return GetEntityType(objectType); - } - - /// - /// Gets the Type of an entity by its - /// - /// - /// Type of the entity - public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) - { - var type = typeof(UmbracoObjectTypes); - var memInfo = type.GetMember(umbracoObjectType.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), - false); - - var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); - if (attribute == null) - throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); - - if (attribute.ModelType == null) - throw new NullReferenceException("The passed in UmbracoObjectType does not contain a Type definition"); - - return attribute.ModelType; - } - - public bool Exists(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var exists = repository.Exists(key); - return exists; - } - } - - public bool Exists(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var exists = repository.Exists(id); - return exists; - } - } - - /// - public int ReserveId(Guid key) - { - NodeDto node; - using (var scope = UowProvider.ScopeProvider.CreateScope()) - { - var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid); - node = scope.Database.SingleOrDefault(sql); - if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); - node = new NodeDto - { - UniqueId = key, - Text = "RESERVED.ID", - NodeObjectType = Constants.ObjectTypes.IdReservationGuid, - - CreateDate = DateTime.Now, - UserId = 0, - ParentId = -1, - Level = 1, - Path = "-1", - SortOrder = 0, - Trashed = false - }; - scope.Database.Insert(node); - scope.Complete(); - } - return node.NodeId; - } - } -} + if (id != Constants.System.Root) + { + var itemPath = itemPaths.FirstOrDefault(x => x.Id == id); + if (itemPath == null) continue; + var path = itemPath.Path; + var qid = id; + clauses.Add(x => x.Path.SqlStartsWith(string.Format("{0},", path), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); + } + } + query.WhereAny(clauses); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Returns a paged collection of descendants from the root + /// + /// + /// + /// + /// + /// + /// + /// + /// true/false to include trashed objects + /// + public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //don't include trashed if specfied + if (includeTrashed == false) + { + query.Where(x => x.Trashed == false); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// An enumerable list of objects + public virtual IEnumerable GetDescendents(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + var pathMatch = entity.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); + + var entities = repository.GetByQuery(query); + return entities; + } + } + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// UmbracoObjectType of the descendents to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); + + var entities = repository.GetByQuery(query, objectTypeId); + return entities; + } + } + + /// + /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. + /// + /// UmbracoObjectType of the root entities to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) + { + //create it once if it is needed (no need for locking here) + if (_rootEntityQuery == null) + { + _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); + } + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); + return entities; + } + } + + /// + /// Gets a collection of all of a given type. + /// + /// Type of the entities to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity + { + var typeFullName = typeof(T).FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + var objectType = _supportedObjectTypes[typeFullName].Item1; + + return GetAll(objectType, ids); + } + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + return ret; + } + } + + public IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, keys); + return ret; + } + } + + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + return repository.GetAllPaths(objectTypeId, ids); + } + } + + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + return repository.GetAllPaths(objectTypeId, keys); + } + } + + /// + /// Gets a collection of + /// + /// Guid id of the UmbracoObjectType + /// + /// An enumerable list of objects + public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) + { + var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + return ret; + } + } + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Id of the entity + /// + public virtual UmbracoObjectTypes GetObjectType(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); + var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); + var objectTypeId = nodeObjectTypeId; + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + } + } + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Unique Id of the entity + /// + public virtual UmbracoObjectTypes GetObjectType(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql().Select("nodeObjectType").From().Where(x => x.UniqueId == key); + var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); + var objectTypeId = nodeObjectTypeId; + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + } + } + + /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. + /// + /// + /// + public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) + { + var entityImpl = entity as UmbracoEntity; + if (entityImpl == null) + return GetObjectType(entity.Id); + + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); + } + + /// + /// Gets the Type of an entity by its Id + /// + /// Id of the entity + /// Type of the entity + public virtual Type GetEntityType(int id) + { + var objectType = GetObjectType(id); + return GetEntityType(objectType); + } + + /// + /// Gets the Type of an entity by its + /// + /// + /// Type of the entity + public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) + { + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), + false); + + var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); + if (attribute == null) + throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); + + if (attribute.ModelType == null) + throw new NullReferenceException("The passed in UmbracoObjectType does not contain a Type definition"); + + return attribute.ModelType; + } + + public bool Exists(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(key); + return exists; + } + } + + public bool Exists(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(id); + return exists; + } + } + + /// + public int ReserveId(Guid key) + { + NodeDto node; + using (var scope = UowProvider.ScopeProvider.CreateScope()) + { + var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid); + node = scope.Database.SingleOrDefault(sql); + if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); + node = new NodeDto + { + UniqueId = key, + Text = "RESERVED.ID", + NodeObjectType = Constants.ObjectTypes.IdReservationGuid, + + CreateDate = DateTime.Now, + UserId = 0, + ParentId = -1, + Level = 1, + Path = "-1", + SortOrder = 0, + Trashed = false + }; + scope.Database.Insert(node); + scope.Complete(); + } + return node.NodeId; + } + } +} diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index d8a60a9cae..a6a2254138 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -1,298 +1,282 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Services -{ - public interface IEntityService - { - /// - /// Returns true if the entity exists - /// - /// - /// - bool Exists(Guid key); - - /// - /// Returns true if the entity exists - /// - /// - /// - bool Exists(int id); - - /// - /// Returns the integer id for a given GUID - /// - /// - /// - /// +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Services +{ + public interface IEntityService + { + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(Guid key); + + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(int id); + + /// + /// Returns the integer id for a given GUID + /// + /// + /// + /// Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); - - /// - /// Returns the integer id for a given Udi - /// - /// - /// - Attempt GetIdForUdi(Udi udi); - - /// - /// Returns the GUID for a given integer id - /// - /// - /// - /// - Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Unique Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Unique Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Unique Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity; - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity; - - /// - /// Gets the parent of entity by its id - /// - /// Id of the entity to retrieve the Parent for - /// An - IUmbracoEntity GetParent(int id); - - /// - /// Gets the parent of entity by its id and UmbracoObjectType - /// - /// Id of the entity to retrieve the Parent for - /// UmbracoObjectType of the parent to retrieve - /// An - IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of children by the parents Id - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - IEnumerable GetChildren(int parentId); - - /// - /// Gets a collection of children by the parents Id and UmbracoObjectType - /// - /// Id of the parent to retrieve children for - /// UmbracoObjectType of the children to retrieve - /// An enumerable list of objects - IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType); - - /// - /// Returns a paged collection of children - /// - /// The parent id to return children for - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants - /// - IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants from the root - /// - /// - /// - /// - /// - /// - /// - /// - /// true/false to include trashed objects - /// - IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// An enumerable list of objects - IEnumerable GetDescendents(int id); - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// UmbracoObjectType of the descendents to retrieve - /// An enumerable list of objects - IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. - /// - /// UmbracoObjectType of the root entities to retrieve - /// An enumerable list of objects - IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of all of a given type. - /// - /// Type of the entities to retrieve - /// An enumerable list of objects - IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids); - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys); - - /// - /// Gets a collection of - /// - /// Guid id of the UmbracoObjectType - /// - /// An enumerable list of objects - IEnumerable GetAll(Guid objectTypeId, params int[] ids); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys); - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Id of the entity - /// - UmbracoObjectTypes GetObjectType(int id); - - /// - /// Gets the UmbracoObjectType from an IUmbracoEntity. - /// - /// - /// - UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); - - /// - /// Gets the Type of an entity by its Id - /// - /// Id of the entity - /// Type of the entity - Type GetEntityType(int id); - - /// - /// Gets the Type of an entity by its - /// - /// - /// Type of the entity - Type GetEntityType(UmbracoObjectTypes umbracoObjectType); - - /// - /// Reserves an identifier for a key. - /// - /// They key. - /// The identifier. - /// When a new content or a media is saved with the key, it will have the reserved identifier. - int ReserveId(Guid key); - } -} \ No newline at end of file + + /// + /// Returns the integer id for a given Udi + /// + /// + /// + Attempt GetIdForUdi(Udi udi); + + /// + /// Returns the GUID for a given integer id + /// + /// + /// + /// + Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Unique Id of the object to retrieve + /// An + IUmbracoEntity GetByKey(Guid key); + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// An + IUmbracoEntity Get(int id); + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Unique Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + /// An + IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + /// An + IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType); + + + /// + /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Type of the model to retrieve. Must be based on an + /// Id of the object to retrieve + /// An + IUmbracoEntity Get(int id) where T : IUmbracoEntity; + + /// + /// Gets the parent of entity by its id + /// + /// Id of the entity to retrieve the Parent for + /// An + IUmbracoEntity GetParent(int id); + + /// + /// Gets the parent of entity by its id and UmbracoObjectType + /// + /// Id of the entity to retrieve the Parent for + /// UmbracoObjectType of the parent to retrieve + /// An + IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of children by the parents Id + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + IEnumerable GetChildren(int parentId); + + /// + /// Gets a collection of children by the parents Id and UmbracoObjectType + /// + /// Id of the parent to retrieve children for + /// UmbracoObjectType of the children to retrieve + /// An enumerable list of objects + IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType); + + /// + /// Returns a paged collection of children + /// + /// The parent id to return children for + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants + /// + IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants from the root + /// + /// + /// + /// + /// + /// + /// + /// + /// true/false to include trashed objects + /// + IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// An enumerable list of objects + IEnumerable GetDescendents(int id); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// UmbracoObjectType of the descendents to retrieve + /// An enumerable list of objects + IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. + /// + /// UmbracoObjectType of the root entities to retrieve + /// An enumerable list of objects + IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of all of a given type. + /// + /// Type of the entities to retrieve + /// An enumerable list of objects + IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids); + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys); + + /// + /// Gets a collection of + /// + /// Guid id of the UmbracoObjectType + /// + /// An enumerable list of objects + IEnumerable GetAll(Guid objectTypeId, params int[] ids); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys); + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Id of the entity + /// + UmbracoObjectTypes GetObjectType(int id); + + /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. + /// + /// + /// + UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); + + /// + /// Gets the Type of an entity by its Id + /// + /// Id of the entity + /// Type of the entity + Type GetEntityType(int id); + + /// + /// Gets the Type of an entity by its + /// + /// + /// Type of the entity + Type GetEntityType(UmbracoObjectTypes umbracoObjectType); + + /// + /// Reserves an identifier for a key. + /// + /// They key. + /// The identifier. + /// When a new content or a media is saved with the key, it will have the reserved identifier. + int ReserveId(Guid key); + } +} diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 6006a6b7c3..b5ca9ee019 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -1,306 +1,298 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Services -{ - public interface IRelationService : IService - { - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelation GetById(int id); - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelationType GetRelationTypeById(int id); - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelationType GetRelationTypeById(Guid id); - - /// - /// Gets a by its Alias - /// - /// Alias of the - /// A object - IRelationType GetRelationTypeByAlias(string alias); - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relations for - /// An enumerable list of objects - IEnumerable GetAllRelations(params int[] ids); - - /// - /// Gets all objects by their - /// - /// to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(RelationType relationType); - - /// - /// Gets all objects by their 's Id - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(int relationTypeId); - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects - IEnumerable GetAllRelationTypes(params int[] ids); - - /// - /// Gets a list of objects by their parent Id - /// - /// Id of the parent to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParentId(int id); - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParent(IUmbracoEntity parent); - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); - - /// - /// Gets a list of objects by their child Id - /// - /// Id of the child to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByChildId(int id); - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByChild(IUmbracoEntity child); - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); - - /// - /// Gets a list of objects by their child or parent Id. - /// Using this method will get you all relations regards of it being a child or parent relation. - /// - /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParentOrChildId(int id); - - IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); - - /// - /// Gets a list of objects by the Name of the - /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeName(string relationTypeName); - - /// - /// Gets a list of objects by the Alias of the - /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeAlias(string relationTypeAlias); - - /// - /// Gets a list of objects by the Id of the - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeId(int relationTypeId); - - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False - /// An - IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Parent object from a Relation as an - /// - /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False - /// An - IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Parent and Child objects from a Relation as a "/> with . - /// - /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False - /// Returns a Tuple with Parent (item1) and Child (item2) - Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); - - /// - /// Gets the Parent objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false); - - /// - /// Gets the Parent and Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of with - IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false); - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// The type of relation to create - /// The created - IRelation Relate(int parentId, int childId, IRelationType relationType); - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// The type of relation to create - /// The created - IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// Alias of the type of relation to create - /// The created - IRelation Relate(int parentId, int childId, string relationTypeAlias); - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// The created - IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); - - /// - /// Checks whether any relations exists for the passed in . - /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False - bool HasRelations(IRelationType relationType); - - /// - /// Checks whether any relations exists for the passed in Id. - /// - /// Id of an object to check relations for - /// Returns True if any relations exists with the given Id, otherwise False - bool IsRelated(int id); - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Returns True if any relations exists with the given Ids, otherwise False - bool AreRelated(int parentId, int childId); - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child); - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(int parentId, int childId, string relationTypeAlias); - - /// - /// Saves a - /// - /// Relation to save - void Save(IRelation relation); - - /// - /// Saves a - /// - /// RelationType to Save - void Save(IRelationType relationType); - - /// - /// Deletes a - /// - /// Relation to Delete - void Delete(IRelation relation); - - /// - /// Deletes a - /// - /// RelationType to Delete - void Delete(IRelationType relationType); - - /// - /// Deletes all objects based on the passed in - /// - /// to Delete Relations for - void DeleteRelationsOfType(IRelationType relationType); - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Services +{ + public interface IRelationService : IService + { + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelation GetById(int id); + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelationType GetRelationTypeById(int id); + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelationType GetRelationTypeById(Guid id); + + /// + /// Gets a by its Alias + /// + /// Alias of the + /// A object + IRelationType GetRelationTypeByAlias(string alias); + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relations for + /// An enumerable list of objects + IEnumerable GetAllRelations(params int[] ids); + + /// + /// Gets all objects by their + /// + /// to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetAllRelationsByRelationType(RelationType relationType); + + /// + /// Gets all objects by their 's Id + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetAllRelationsByRelationType(int relationTypeId); + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relationtypes for + /// An enumerable list of objects + IEnumerable GetAllRelationTypes(params int[] ids); + + /// + /// Gets a list of objects by their parent Id + /// + /// Id of the parent to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParentId(int id); + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParent(IUmbracoEntity parent); + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); + + /// + /// Gets a list of objects by their child Id + /// + /// Id of the child to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByChildId(int id); + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByChild(IUmbracoEntity child); + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); + + /// + /// Gets a list of objects by their child or parent Id. + /// Using this method will get you all relations regards of it being a child or parent relation. + /// + /// Id of the child or parent to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParentOrChildId(int id); + + IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); + + /// + /// Gets a list of objects by the Name of the + /// + /// Name of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeName(string relationTypeName); + + /// + /// Gets a list of objects by the Alias of the + /// + /// Alias of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeAlias(string relationTypeAlias); + + /// + /// Gets a list of objects by the Id of the + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeId(int relationTypeId); + + /// + /// Gets the Child object from a Relation as an + /// + /// Relation to retrieve child object from + /// An + IUmbracoEntity GetChildEntityFromRelation(IRelation relation); + + /// + /// Gets the Parent object from a Relation as an + /// + /// Relation to retrieve parent object from + /// An + IUmbracoEntity GetParentEntityFromRelation(IRelation relation); + + /// + /// Gets the Parent and Child objects from a Relation as a "/> with . + /// + /// Relation to retrieve parent and child object from + /// Returns a Tuple with Parent (item1) and Child (item2) + Tuple GetEntitiesFromRelation(IRelation relation); + + /// + /// Gets the Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve child objects from + /// An enumerable list of + IEnumerable GetChildEntitiesFromRelations(IEnumerable relations); + + /// + /// Gets the Parent objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent objects from + /// An enumerable list of + IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); + + /// + /// Gets the Parent and Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent and child objects from + /// Optional bool to load the complete object graph when set to False + IEnumerable> GetEntitiesFromRelations( + IEnumerable relations); + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// The type of relation to create + /// The created + IRelation Relate(int parentId, int childId, IRelationType relationType); + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// The type of relation to create + /// The created + IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// Alias of the type of relation to create + /// The created + IRelation Relate(int parentId, int childId, string relationTypeAlias); + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// The created + IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); + + /// + /// Checks whether any relations exists for the passed in . + /// + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False + bool HasRelations(IRelationType relationType); + + /// + /// Checks whether any relations exists for the passed in Id. + /// + /// Id of an object to check relations for + /// Returns True if any relations exists with the given Id, otherwise False + bool IsRelated(int id); + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Returns True if any relations exists with the given Ids, otherwise False + bool AreRelated(int parentId, int childId); + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child); + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(int parentId, int childId, string relationTypeAlias); + + /// + /// Saves a + /// + /// Relation to save + void Save(IRelation relation); + + /// + /// Saves a + /// + /// RelationType to Save + void Save(IRelationType relationType); + + /// + /// Deletes a + /// + /// Relation to Delete + void Delete(IRelation relation); + + /// + /// Deletes a + /// + /// RelationType to Delete + void Delete(IRelationType relationType); + + /// + /// Deletes all objects based on the passed in + /// + /// to Delete Relations for + void DeleteRelationsOfType(IRelationType relationType); + } +} diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index ccf86d425d..a940096ded 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -1,748 +1,740 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Services -{ - public class RelationService : ScopeRepositoryService, IRelationService - { - private readonly IEntityService _entityService; - - public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService) - : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) - { - if (entityService == null) throw new ArgumentNullException("entityService"); - _entityService = entityService; - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelation GetById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelationType GetRelationTypeById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelationType GetRelationTypeById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Alias - /// - /// Alias of the - /// A object - public IRelationType GetRelationTypeByAlias(string alias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Alias == alias); - return repository.GetByQuery(query).FirstOrDefault(); - } - } - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relations for - /// An enumerable list of objects - public IEnumerable GetAllRelations(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets all objects by their - /// - /// to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(RelationType relationType) - { - return GetAllRelationsByRelationType(relationType.Id); - } - - /// - /// Gets all objects by their 's Id - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(int relationTypeId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationTypeId); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects - public IEnumerable GetAllRelationTypes(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets a list of objects by their parent Id - /// - /// Id of the parent to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParentId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent) - { - return GetByParentId(parent.Id); - } - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) - { - return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } - - /// - /// Gets a list of objects by their child Id - /// - /// Id of the child to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChildId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ChildId == id); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child) - { - return GetByChildId(child.Id); - } - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) - { - return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } - - /// - /// Gets a list of objects by their child or parent Id. - /// Using this method will get you all relations regards of it being a child or parent relation. - /// - /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParentOrChildId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); - return repository.GetByQuery(query); - } - } - - public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var typeRepository = RepositoryFactory.CreateRelationTypeRepository(uow); - var rtQuery = new Query().Where(x => x.Alias == relationTypeAlias); - var relationType = typeRepository.GetByQuery(rtQuery).FirstOrDefault(); - if (relationType == null) return Enumerable.Empty(); - - var relationRepo = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id); - return relationRepo.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by the Name of the - /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeName(string relationTypeName) - { - List relationTypeIds = null; - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Name == relationTypeName); - var relationTypes = repository.GetByQuery(query); - if (relationTypes.Any()) - { - relationTypeIds = relationTypes.Select(x => x.Id).ToList(); - } - } - - if (relationTypeIds == null) - return Enumerable.Empty(); - - return GetRelationsByListOfTypeIds(relationTypeIds); - } - - /// - /// Gets a list of objects by the Alias of the - /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) - { - List relationTypeIds = null; - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Alias == relationTypeAlias); - var relationTypes = repository.GetByQuery(query); - if (relationTypes.Any()) - { - relationTypeIds = relationTypes.Select(x => x.Id).ToList(); - } - } - - if (relationTypeIds == null) - return Enumerable.Empty(); - - return GetRelationsByListOfTypeIds(relationTypeIds); - } - - /// - /// Gets a list of objects by the Id of the - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeId(int relationTypeId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationTypeId); - return repository.GetByQuery(query); - } - } - - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False - /// An - public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - return _entityService.Get(relation.ChildId, objectType, loadBaseType); - } - - /// - /// Gets the Parent object from a Relation as an - /// - /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False - /// An - public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - return _entityService.Get(relation.ParentId, objectType, loadBaseType); - } - - /// - /// Gets the Parent and Child objects from a Relation as a "/> with . - /// - /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False - /// Returns a Tuple with Parent (item1) and Child (item2) - public Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false) - { - var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); - - return new Tuple(parent, child); - } - - /// - /// Gets the Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) - { - foreach (var relation in relations) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - yield return _entityService.Get(relation.ChildId, objectType, loadBaseType); - } - } - - /// - /// Gets the Parent objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false) - { - foreach (var relation in relations) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - yield return _entityService.Get(relation.ParentId, objectType, loadBaseType); - } - } - - /// - /// Gets the Parent and Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of with - public IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false) - { - foreach (var relation in relations) - { - var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); - - yield return new Tuple(parent, child); - } - } - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// The type of relation to create - /// The created - public IRelation Relate(int parentId, int childId, IRelationType relationType) - { - // Ensure that the RelationType has an indentity before using it to relate two entities - if (relationType.HasIdentity == false) - Save(relationType); - - var relation = new Relation(parentId, childId, relationType); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relation); - if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) - { - uow.Commit(); - return relation; - } - - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.AddOrUpdate(relation); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelation, this, saveEventArgs); - return relation; - } - } - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// The type of relation to create - /// The created - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) - { - return Relate(parent.Id, child.Id, relationType); - } - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// Alias of the type of relation to create - /// The created - public IRelation Relate(int parentId, int childId, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); - - return Relate(parentId, childId, relationType); - } - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// The created - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); - - return Relate(parent.Id, child.Id, relationType); - } - - /// - /// Checks whether any relations exists for the passed in . - /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False - public bool HasRelations(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationType.Id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether any relations exists for the passed in Id. - /// - /// Id of an object to check relations for - /// Returns True if any relations exists with the given Id, otherwise False - public bool IsRelated(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Returns True if any relations exists with the given Ids, otherwise False - public bool AreRelated(int parentId, int childId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related with a given relation type alias - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Alias of the relation type - /// Returns True if any relations exists with the given Ids and relation type, otherwise False - public bool AreRelated(int parentId, int childId, string relationTypeAlias) - { - var relType = GetRelationTypeByAlias(relationTypeAlias); - if (relType == null) - return false; - - return AreRelated(parentId, childId, relType); - } - - - /// - /// Checks whether two items are related with a given relation type - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Type of relation - /// Returns True if any relations exists with the given Ids and relation type, otherwise False - public bool AreRelated(int parentId, int childId, IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Returns True if any relations exist between the entities, otherwise False - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) - { - return AreRelated(parent.Id, child.Id); - } - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) - { - return AreRelated(parent.Id, child.Id, relationTypeAlias); - } - - - /// - /// Saves a - /// - /// Relation to save - public void Save(IRelation relation) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relation); - if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.AddOrUpdate(relation); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelation, this, saveEventArgs); - } - } - - /// - /// Saves a - /// - /// RelationType to Save - public void Save(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relationType); - if (uow.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - repository.AddOrUpdate(relationType); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelationType, this, saveEventArgs); - } - } - - /// - /// Deletes a - /// - /// Relation to Delete - public void Delete(IRelation relation) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(relation); - if (uow.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.Delete(relation); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedRelation, this, deleteEventArgs); - } - } - - /// - /// Deletes a - /// - /// RelationType to Delete - public void Delete(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(relationType); - if (uow.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - repository.Delete(relationType); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedRelationType, this, deleteEventArgs); - } - } - - /// - /// Deletes all objects based on the passed in - /// - /// to Delete Relations for - public void DeleteRelationsOfType(IRelationType relationType) - { - var relations = new List(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationType.Id); - relations.AddRange(repository.GetByQuery(query).ToList()); - - foreach (var relation in relations) - { - repository.Delete(relation); - } - uow.Commit(); - uow.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs(relations, false)); - } - } - - #region Private Methods - private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) - { - var relations = new List(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - foreach (var relationTypeId in relationTypeIds) - { - int id = relationTypeId; - var query = new Query().Where(x => x.RelationTypeId == id); - relations.AddRange(repository.GetByQuery(query).ToList()); - } - } - return relations; - } - #endregion - - #region Events Handlers - /// - /// Occurs before Deleting a Relation - /// - public static event TypedEventHandler> DeletingRelation; - - /// - /// Occurs after a Relation is Deleted - /// - public static event TypedEventHandler> DeletedRelation; - - /// - /// Occurs before Saving a Relation - /// - public static event TypedEventHandler> SavingRelation; - - /// - /// Occurs after a Relation is Saved - /// - public static event TypedEventHandler> SavedRelation; - - /// - /// Occurs before Deleting a RelationType - /// - public static event TypedEventHandler> DeletingRelationType; - - /// - /// Occurs after a RelationType is Deleted - /// - public static event TypedEventHandler> DeletedRelationType; - - /// - /// Occurs before Saving a RelationType - /// - public static event TypedEventHandler> SavingRelationType; - - /// - /// Occurs after a RelationType is Saved - /// - public static event TypedEventHandler> SavedRelationType; - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class RelationService : ScopeRepositoryService, IRelationService + { + private readonly IEntityService _entityService; + + public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService) + : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) + { + if (entityService == null) throw new ArgumentNullException("entityService"); + _entityService = entityService; + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelation GetById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelationType GetRelationTypeById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelationType GetRelationTypeById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Alias + /// + /// Alias of the + /// A object + public IRelationType GetRelationTypeByAlias(string alias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Alias == alias); + return repository.GetByQuery(query).FirstOrDefault(); + } + } + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relations for + /// An enumerable list of objects + public IEnumerable GetAllRelations(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets all objects by their + /// + /// to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetAllRelationsByRelationType(RelationType relationType) + { + return GetAllRelationsByRelationType(relationType.Id); + } + + /// + /// Gets all objects by their 's Id + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetAllRelationsByRelationType(int relationTypeId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationTypeId); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relationtypes for + /// An enumerable list of objects + public IEnumerable GetAllRelationTypes(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets a list of objects by their parent Id + /// + /// Id of the parent to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParentId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParent(IUmbracoEntity parent) + { + return GetByParentId(parent.Id); + } + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) + { + return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias); + } + + /// + /// Gets a list of objects by their child Id + /// + /// Id of the child to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByChildId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ChildId == id); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByChild(IUmbracoEntity child) + { + return GetByChildId(child.Id); + } + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) + { + return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias); + } + + /// + /// Gets a list of objects by their child or parent Id. + /// Using this method will get you all relations regards of it being a child or parent relation. + /// + /// Id of the child or parent to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParentOrChildId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); + return repository.GetByQuery(query); + } + } + + public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var typeRepository = RepositoryFactory.CreateRelationTypeRepository(uow); + var rtQuery = new Query().Where(x => x.Alias == relationTypeAlias); + var relationType = typeRepository.GetByQuery(rtQuery).FirstOrDefault(); + if (relationType == null) return Enumerable.Empty(); + + var relationRepo = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id); + return relationRepo.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by the Name of the + /// + /// Name of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeName(string relationTypeName) + { + List relationTypeIds = null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Name == relationTypeName); + var relationTypes = repository.GetByQuery(query); + if (relationTypes.Any()) + { + relationTypeIds = relationTypes.Select(x => x.Id).ToList(); + } + } + + if (relationTypeIds == null) + return Enumerable.Empty(); + + return GetRelationsByListOfTypeIds(relationTypeIds); + } + + /// + /// Gets a list of objects by the Alias of the + /// + /// Alias of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) + { + List relationTypeIds = null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Alias == relationTypeAlias); + var relationTypes = repository.GetByQuery(query); + if (relationTypes.Any()) + { + relationTypeIds = relationTypes.Select(x => x.Id).ToList(); + } + } + + if (relationTypeIds == null) + return Enumerable.Empty(); + + return GetRelationsByListOfTypeIds(relationTypeIds); + } + + /// + /// Gets a list of objects by the Id of the + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeId(int relationTypeId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationTypeId); + return repository.GetByQuery(query); + } + } + + /// + /// Gets the Child object from a Relation as an + /// + /// Relation to retrieve child object from + /// An + public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + return _entityService.Get(relation.ChildId, objectType); + } + + /// + /// Gets the Parent object from a Relation as an + /// + /// Relation to retrieve parent object from + /// An + public IUmbracoEntity GetParentEntityFromRelation(IRelation relation) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + return _entityService.Get(relation.ParentId, objectType); + } + + /// + /// Gets the Parent and Child objects from a Relation as a "/> with . + /// + /// Relation to retrieve parent and child object from + /// Returns a Tuple with Parent (item1) and Child (item2) + public Tuple GetEntitiesFromRelation(IRelation relation) + { + var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); + + return new Tuple(parent, child); + } + + /// + /// Gets the Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve child objects from + /// An enumerable list of + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) + { + foreach (var relation in relations) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + yield return _entityService.Get(relation.ChildId, objectType); + } + } + + /// + /// Gets the Parent objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent objects from + /// An enumerable list of + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) + { + foreach (var relation in relations) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + yield return _entityService.Get(relation.ParentId, objectType); + } + } + + /// + /// Gets the Parent and Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent and child objects from + /// An enumerable list of with + public IEnumerable> GetEntitiesFromRelations( + IEnumerable relations) + { + foreach (var relation in relations) + { + var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); + + yield return new Tuple(parent, child); + } + } + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// The type of relation to create + /// The created + public IRelation Relate(int parentId, int childId, IRelationType relationType) + { + // Ensure that the RelationType has an indentity before using it to relate two entities + if (relationType.HasIdentity == false) + Save(relationType); + + var relation = new Relation(parentId, childId, relationType); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relation); + if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) + { + uow.Commit(); + return relation; + } + + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.AddOrUpdate(relation); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelation, this, saveEventArgs); + return relation; + } + } + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// The type of relation to create + /// The created + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) + { + return Relate(parent.Id, child.Id, relationType); + } + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// Alias of the type of relation to create + /// The created + public IRelation Relate(int parentId, int childId, string relationTypeAlias) + { + var relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) + throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + + return Relate(parentId, childId, relationType); + } + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// The created + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + { + var relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) + throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + + return Relate(parent.Id, child.Id, relationType); + } + + /// + /// Checks whether any relations exists for the passed in . + /// + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False + public bool HasRelations(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationType.Id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether any relations exists for the passed in Id. + /// + /// Id of an object to check relations for + /// Returns True if any relations exists with the given Id, otherwise False + public bool IsRelated(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Returns True if any relations exists with the given Ids, otherwise False + public bool AreRelated(int parentId, int childId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related with a given relation type alias + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Alias of the relation type + /// Returns True if any relations exists with the given Ids and relation type, otherwise False + public bool AreRelated(int parentId, int childId, string relationTypeAlias) + { + var relType = GetRelationTypeByAlias(relationTypeAlias); + if (relType == null) + return false; + + return AreRelated(parentId, childId, relType); + } + + + /// + /// Checks whether two items are related with a given relation type + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Type of relation + /// Returns True if any relations exists with the given Ids and relation type, otherwise False + public bool AreRelated(int parentId, int childId, IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Returns True if any relations exist between the entities, otherwise False + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) + { + return AreRelated(parent.Id, child.Id); + } + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + { + return AreRelated(parent.Id, child.Id, relationTypeAlias); + } + + + /// + /// Saves a + /// + /// Relation to save + public void Save(IRelation relation) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relation); + if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.AddOrUpdate(relation); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelation, this, saveEventArgs); + } + } + + /// + /// Saves a + /// + /// RelationType to Save + public void Save(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relationType); + if (uow.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + repository.AddOrUpdate(relationType); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelationType, this, saveEventArgs); + } + } + + /// + /// Deletes a + /// + /// Relation to Delete + public void Delete(IRelation relation) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(relation); + if (uow.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.Delete(relation); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedRelation, this, deleteEventArgs); + } + } + + /// + /// Deletes a + /// + /// RelationType to Delete + public void Delete(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(relationType); + if (uow.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + repository.Delete(relationType); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedRelationType, this, deleteEventArgs); + } + } + + /// + /// Deletes all objects based on the passed in + /// + /// to Delete Relations for + public void DeleteRelationsOfType(IRelationType relationType) + { + var relations = new List(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationType.Id); + relations.AddRange(repository.GetByQuery(query).ToList()); + + foreach (var relation in relations) + { + repository.Delete(relation); + } + uow.Commit(); + uow.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs(relations, false)); + } + } + + #region Private Methods + private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) + { + var relations = new List(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + foreach (var relationTypeId in relationTypeIds) + { + int id = relationTypeId; + var query = new Query().Where(x => x.RelationTypeId == id); + relations.AddRange(repository.GetByQuery(query).ToList()); + } + } + return relations; + } + #endregion + + #region Events Handlers + /// + /// Occurs before Deleting a Relation + /// + public static event TypedEventHandler> DeletingRelation; + + /// + /// Occurs after a Relation is Deleted + /// + public static event TypedEventHandler> DeletedRelation; + + /// + /// Occurs before Saving a Relation + /// + public static event TypedEventHandler> SavingRelation; + + /// + /// Occurs after a Relation is Saved + /// + public static event TypedEventHandler> SavedRelation; + + /// + /// Occurs before Deleting a RelationType + /// + public static event TypedEventHandler> DeletingRelationType; + + /// + /// Occurs after a RelationType is Deleted + /// + public static event TypedEventHandler> DeletedRelationType; + + /// + /// Occurs before Saving a RelationType + /// + public static event TypedEventHandler> SavingRelationType; + + /// + /// Occurs after a RelationType is Saved + /// + public static event TypedEventHandler> SavedRelationType; + #endregion + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 636437e387..2f31d36b62 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -4,7 +4,7 @@ * @description A helper object used for dealing with media items **/ function mediaHelper(umbRequestHelper) { - + //container of fileresolvers var _mediaFileResolvers = {}; @@ -13,11 +13,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the file path associated with the media property if there is one - * + * * @param {object} options Options object * @param {object} options.mediaModel The media object to retrieve the image path from * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image @@ -75,16 +75,16 @@ function mediaHelper(umbRequestHelper) { return ""; }, - + /** * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the actual image path associated with the image property if there is one - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -104,11 +104,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnail * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * formats the display model used to display the content to the model used to save the content - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -133,54 +133,48 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#resolveFileFromEntity * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media entity returned with the entityResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ - resolveFileFromEntity : function(mediaEntity, thumbnail) { - - if (!angular.isObject(mediaEntity.metaData)) { + resolveFileFromEntity: function (mediaEntity, thumbnail) { + + if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; } - var values = _.values(mediaEntity.metaData); - for (var i = 0; i < values.length; i++) { - var val = values[i]; - if (angular.isObject(val) && val.PropertyEditorAlias) { - for (var resolver in _mediaFileResolvers) { - if (val.PropertyEditorAlias === resolver) { - //we need to format a property variable that coincides with how the property would be structured - // if it came from the mediaResource just to keep things slightly easier for the file resolvers. - var property = { value: val.Value }; - - return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); - } - } + if (thumbnail) { + if (this.detectIfImageByExtension(mediaEntity.metaData.MediaPath)) { + return this.getThumbnailFromPath(mediaEntity.metaData.MediaPath); + } + else { + return null; } } - - return ""; + else { + return mediaEntity.metaData.MediaPath; + } }, /** * @ngdoc function * @name umbraco.services.mediaHelper#resolveFile * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media object returned with the mediaResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ /*jshint loopfunc: true */ resolveFile : function(mediaItem, thumbnail){ - + function iterateProps(props){ var res = null; for(var resolver in _mediaFileResolvers) { @@ -191,7 +185,7 @@ function mediaHelper(umbRequestHelper) { } } - return res; + return res; } //we either have properties raw on the object, or spread out on tabs @@ -208,7 +202,7 @@ function mediaHelper(umbRequestHelper) { } } } - return result; + return result; }, /*jshint loopfunc: true */ @@ -246,11 +240,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios - * + * * @param {number} maxSize Maximum width & height * @param {number} width Current width * @param {number} height Current height @@ -289,11 +283,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnailFromPath * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the path to the thumbnail version of a given media library image path - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ getThumbnailFromPath: function (imagePath) { @@ -319,11 +313,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#detectIfImageByExtension * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns true/false, indicating if the given path has an allowed image extension - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ detectIfImageByExtension: function (imagePath) { @@ -331,7 +325,7 @@ function mediaHelper(umbRequestHelper) { if (!imagePath) { return false; } - + var lowered = imagePath.toLowerCase(); var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1; @@ -388,6 +382,6 @@ function mediaHelper(umbRequestHelper) { var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ext; } - + }; }angular.module('umbraco.services').factory('mediaHelper', mediaHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index f337dbce18..da7ca307ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,134 +1,140 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Dialogs.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { - - var dialogOptions = $scope.dialogOptions; - - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function(e) { - if (e.keyCode === 13) { - e.preventDefault(); - - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - $scope.showFolderInput = false; - $scope.newFolderName = ""; - - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - - $scope.gotoFolder(data); - }); - } - }; - - $scope.gotoFolder = function(folder) { - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { - // anc.splice(0,1); - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - } else { - $scope.path = []; - } - - //mediaResource.rootMedia() - mediaResource.getChildren(folder.id) - .then(function(data) { - $scope.searchTerm = ""; - $scope.images = data.items ? data.items : []; - }); - - $scope.currentFolder = folder; - }; - - - $scope.clickHandler = function(image, ev, select) { - ev.preventDefault(); - - if (image.isFolder && !select) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - - //we have 3 options add to collection (if multi) show details, or submit it right back to the callback - if ($scope.multiPicker) { - $scope.select(image); - image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : ""; - } else if ($scope.showDetails) { - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - } else { - $scope.submit(image); - } - } - }; - - $scope.exitDetails = function() { - if (!$scope.currentFolder) { - $scope.gotoFolder(); - } - - $scope.target = undefined; - }; - - $scope.onUploadComplete = function() { - $scope.gotoFolder($scope.currentFolder); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - //default root item - if (!$scope.target) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - }); \ No newline at end of file +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Dialogs.MediaPickerController", + function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { + + var dialogOptions = $scope.dialogOptions; + + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function(e) { + if (e.keyCode === 13) { + e.preventDefault(); + + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + $scope.showFolderInput = false; + $scope.newFolderName = ""; + + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + + $scope.gotoFolder(data); + }); + } + }; + + $scope.gotoFolder = function(folder) { + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media") + .then(function(anc) { + // anc.splice(0,1); + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + } else { + $scope.path = []; + } + + //mediaResource.rootMedia() + entityResource.getChildren(folder.id, "Media") + .then(function(data) { + for (i=0;i -1) ? "selected" : ""; + } else if ($scope.showDetails) { + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + } else { + $scope.submit(image); + } + } + }; + + $scope.exitDetails = function() { + if (!$scope.currentFolder) { + $scope.gotoFolder(); + } + + $scope.target = undefined; + }; + + $scope.onUploadComplete = function() { + $scope.gotoFolder($scope.currentFolder); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + //default root item + if (!$scope.target) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index e7d14c9871..400d3a7bb5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -90,7 +90,7 @@ angular.module("umbraco") var id = $scope.target.udi ? $scope.target.udi : $scope.target.id var altText = $scope.target.altText; if (id) { - mediaResource.getById(id) + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -388,10 +388,17 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id, { dataTypeId: $scope.model.dataTypeId }) + return entityResource.getChildren(id, "Media") .then(function(data) { + + for (i=0;i - /// Summary description for Access. - /// - [Obsolete("Use Umbraco.Core.Service.IPublicAccessService instead")] - public class Access - { - - [Obsolete("Do not access this property directly, it is not thread safe, use GetXmlDocumentCopy instead")] - public static XmlDocument AccessXml - { - get { return GetXmlDocumentCopy(); } - } - - //NOTE: This is here purely for backwards compat - [Obsolete("This should never be used, the data is stored in the database now")] - public static XmlDocument GetXmlDocumentCopy() - { - var allAccessEntries = ApplicationContext.Current.Services.PublicAccessService.GetAll().ToArray(); - - var xml = XDocument.Parse(""); - foreach (var entry in allAccessEntries) - { - var pageXml = new XElement("page", - new XAttribute("id", entry.ProtectedNodeId), - new XAttribute("loginPage", entry.LoginNodeId), - new XAttribute("noRightsPage", entry.NoAccessNodeId)); - - foreach (var rule in entry.Rules) - { - if (rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) - { - //if there is a member id claim then it is 'simple' (this is how legacy worked) - pageXml.Add(new XAttribute("simple", "True")); - pageXml.Add(new XAttribute("memberId", rule.RuleValue)); - } - else if (rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) - { - pageXml.Add(new XElement("group", new XAttribute("id", rule.RuleValue))); - } - } - - xml.Root.Add(pageXml); - } - - return xml.ToXmlDocument(); - } - - #region Manipulation methods - - public static void AddMembershipRoleToDocument(int documentId, string role) - { - //event - var doc = new Document(documentId); - var e = new AddMemberShipRoleToDocumentEventArgs(); - new Access().FireBeforeAddMemberShipRoleToDocument(doc, role, e); - - if (e.Cancel) return; - - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - Save(); - - new Access().FireAfterAddMemberShipRoleToDocument(doc, role, e); - } - - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void AddMemberGroupToDocument(int DocumentId, int MemberGroupId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - - if (content == null) - throw new Exception("No content found with document id " + DocumentId); - - if (ApplicationContext.Current.Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberGroupIdRuleType, - MemberGroupId.ToString(CultureInfo.InvariantCulture))) - { - Save(); - } - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void AddMemberToDocument(int DocumentId, int MemberId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - - if (content == null) - throw new Exception("No content found with document id " + DocumentId); - - if (ApplicationContext.Current.Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberIdRuleType, - MemberId.ToString(CultureInfo.InvariantCulture))) - { - Save(); - } - - } - - public static void AddMembershipUserToDocument(int documentId, string membershipUserName) - { - //event - var doc = new Document(documentId); - var e = new AddMembershipUserToDocumentEventArgs(); - new Access().FireBeforeAddMembershipUserToDocument(doc, membershipUserName, e); - - if (e.Cancel) return; - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberUsernameRuleType, - membershipUserName); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - if (entry) - { - Save(); - new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); - } - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void RemoveMemberGroupFromDocument(int DocumentId, int MemberGroupId) - { - var doc = new Document(DocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberGroupIdRuleType, - MemberGroupId.ToString(CultureInfo.InvariantCulture)); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - if (entry) - { - Save(); - } - } - - public static void RemoveMembershipRoleFromDocument(int documentId, string role) - { - var doc = new Document(documentId); - var e = new RemoveMemberShipRoleFromDocumentEventArgs(); - new Access().FireBeforeRemoveMemberShipRoleFromDocument(doc, role, e); - - if (e.Cancel) return; - - if (ApplicationContext.Current.Services.PublicAccessService.RemoveRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role)) - { - Save(); - new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); - }; - - } - - public static bool RenameMemberShipRole(string oldRolename, string newRolename) - { - var hasChange = ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(oldRolename, newRolename); - - if (hasChange) - Save(); - - return hasChange; - } - - public static void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) - { - var doc = new Document(DocumentId); - var e = new AddProtectionEventArgs(); - new Access().FireBeforeAddProtection(doc, e); - - if (e.Cancel) return; - - var loginContent = ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId); - if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); - var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); - if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity.Id.ToString()); - if (entry != null) - { - if (Simple) - { - // if using simple mode, make sure that all existing groups are removed - entry.ClearRules(); - } - - //ensure the correct ids are applied - entry.LoginNodeId = loginContent.Id; - entry.NoAccessNodeId = noAccessContent.Id; - } - else - { - entry = new PublicAccessEntry(doc.ContentEntity, - ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId), - ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId), - new List()); - } - - if (ApplicationContext.Current.Services.PublicAccessService.Save(entry)) - { - Save(); - new Access().FireAfterAddProtection(new Document(DocumentId), e); - } - - } - - public static void RemoveProtection(int DocumentId) - { - //event - var doc = new Document(DocumentId); - var e = new RemoveProtectionEventArgs(); - new Access().FireBeforeRemoveProtection(doc, e); - - if (e.Cancel) return; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); - if (entry != null) - { - ApplicationContext.Current.Services.PublicAccessService.Delete(entry); - } - - Save(); - - new Access().FireAfterRemoveProtection(doc, e); - } - #endregion - - #region Reading methods - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static bool IsProtectedByGroup(int DocumentId, int GroupId) - { - var d = new Document(DocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(d.ContentEntity); - if (entry == null) return false; - - return entry.Rules - .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType - && x.RuleValue == GroupId.ToString(CultureInfo.InvariantCulture)); - - } - - public static bool IsProtectedByMembershipRole(int documentId, string role) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return false; - - return entry.Rules - .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && x.RuleValue == role); - - } - - public static string[] GetAccessingMembershipRoles(int documentId, string path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); - if (entry == null) return new string[] { }; - - var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); - return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static member.MemberGroup[] GetAccessingGroups(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - var memberGroupIdRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType); - - return memberGroupIdRules.Select(x => new member.MemberGroup(int.Parse(x.RuleValue))).ToArray(); - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static member.Member GetAccessingMember(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - //legacy would throw an exception here if it was not 'simple' and simple means based on a member id in this case - if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberIdRuleType)) - { - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - } - - var memberIdRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType); - return new member.Member(int.Parse(memberIdRule.RuleValue)); - - } - - public static MembershipUser GetAccessingMembershipUser(int documentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - //legacy would throw an exception here if it was not 'simple' and simple means based on a username - if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) - { - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - } - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); - return provider.GetUser(usernameRule.RuleValue, false); - - } - - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static bool HasAccess(int DocumentId, member.Member Member) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return true; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return true; - - var memberGroupIds = Member.Groups.Values.Cast().Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType - && memberGroupIds.Contains(x.RuleValue)); - - } - - [Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)] - public static bool HasAccces(int documentId, object memberId) - { - // Call the correctly named version of this method - return HasAccess(documentId, memberId); - } - - public static bool HasAccess(int documentId, object memberId) - { - return ApplicationContext.Current.Services.PublicAccessService.HasAccess( - documentId, - memberId, - ApplicationContext.Current.Services.ContentService, - MembershipProviderExtensions.GetMembersMembershipProvider(), - //TODO: This should really be targeting a specific provider by name!! - Roles.Provider); - } - - public static bool HasAccess(int documentId, string path, MembershipUser member) - { - return ApplicationContext.Current.Services.PublicAccessService.HasAccess( - path, - member, - //TODO: This should really be targeting a specific provider by name!! - Roles.Provider); - } - - public static ProtectionType GetProtectionType(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return ProtectionType.NotProtected; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return ProtectionType.NotProtected; - - //legacy states that if it is protected by a member id then it is 'simple' - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType) - ? ProtectionType.Simple - : ProtectionType.Advanced; - - } - - public static bool IsProtected(int DocumentId, string Path) - { - return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); - } - - //return the protection status of this exact document - not based on inheritance - public static bool IsProtected(int DocumentId) - { - return ApplicationContext.Current.Services.PublicAccessService.IsProtected(DocumentId.ToString()); - } - - public static int GetErrorPage(string Path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); - if (entry == null) return -1; - var entity = ApplicationContext.Current.Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - - } - - public static int GetLoginPage(string Path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); - if (entry == null) return -1; - var entity = ApplicationContext.Current.Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - - } - #endregion - - - //NOTE: This is purely here for backwards compat for events - private static void Save() - { - var e = new SaveEventArgs(); - - new Access().FireBeforeSave(e); - - if (e.Cancel) return; - - new Access().FireAfterSave(e); - } - - - //Event delegates - public delegate void SaveEventHandler(Access sender, SaveEventArgs e); - - public delegate void AddProtectionEventHandler(Document sender, AddProtectionEventArgs e); - public delegate void RemoveProtectionEventHandler(Document sender, RemoveProtectionEventArgs e); - - public delegate void AddMemberShipRoleToDocumentEventHandler(Document sender, string role, AddMemberShipRoleToDocumentEventArgs e); - public delegate void RemoveMemberShipRoleFromDocumentEventHandler(Document sender, string role, RemoveMemberShipRoleFromDocumentEventArgs e); - - public delegate void RemoveMemberShipUserFromDocumentEventHandler(Document sender, string MembershipUserName, RemoveMemberShipUserFromDocumentEventArgs e); - public delegate void AddMembershipUserToDocumentEventHandler(Document sender, string MembershipUserName, AddMembershipUserToDocumentEventArgs e); - - //Events - - public static event SaveEventHandler BeforeSave; - protected virtual void FireBeforeSave(SaveEventArgs e) - { - if (BeforeSave != null) - BeforeSave(this, e); - } - - public static event SaveEventHandler AfterSave; - protected virtual void FireAfterSave(SaveEventArgs e) - { - if (AfterSave != null) - AfterSave(this, e); - } - - public static event AddProtectionEventHandler BeforeAddProtection; - protected virtual void FireBeforeAddProtection(Document doc, AddProtectionEventArgs e) - { - if (BeforeAddProtection != null) - BeforeAddProtection(doc, e); - } - - public static event AddProtectionEventHandler AfterAddProtection; - protected virtual void FireAfterAddProtection(Document doc, AddProtectionEventArgs e) - { - if (AfterAddProtection != null) - AfterAddProtection(doc, e); - } - - public static event RemoveProtectionEventHandler BeforeRemoveProtection; - protected virtual void FireBeforeRemoveProtection(Document doc, RemoveProtectionEventArgs e) - { - if (BeforeRemoveProtection != null) - BeforeRemoveProtection(doc, e); - } - - public static event RemoveProtectionEventHandler AfterRemoveProtection; - protected virtual void FireAfterRemoveProtection(Document doc, RemoveProtectionEventArgs e) - { - if (AfterRemoveProtection != null) - AfterRemoveProtection(doc, e); - } - - public static event AddMemberShipRoleToDocumentEventHandler BeforeAddMemberShipRoleToDocument; - protected virtual void FireBeforeAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) - { - if (BeforeAddMemberShipRoleToDocument != null) - BeforeAddMemberShipRoleToDocument(doc, role, e); - } - - public static event AddMemberShipRoleToDocumentEventHandler AfterAddMemberShipRoleToDocument; - protected virtual void FireAfterAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) - { - if (AfterAddMemberShipRoleToDocument != null) - AfterAddMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipRoleFromDocumentEventHandler BeforeRemoveMemberShipRoleToDocument; - protected virtual void FireBeforeRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) - { - if (BeforeRemoveMemberShipRoleToDocument != null) - BeforeRemoveMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipRoleFromDocumentEventHandler AfterRemoveMemberShipRoleToDocument; - protected virtual void FireAfterRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) - { - if (AfterRemoveMemberShipRoleToDocument != null) - AfterRemoveMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipUserFromDocumentEventHandler BeforeRemoveMembershipUserFromDocument; - protected virtual void FireBeforeRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) - { - if (BeforeRemoveMembershipUserFromDocument != null) - BeforeRemoveMembershipUserFromDocument(doc, username, e); - } - - public static event RemoveMemberShipUserFromDocumentEventHandler AfterRemoveMembershipUserFromDocument; - protected virtual void FireAfterRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) - { - if (AfterRemoveMembershipUserFromDocument != null) - AfterRemoveMembershipUserFromDocument(doc, username, e); - } - - public static event AddMembershipUserToDocumentEventHandler BeforeAddMembershipUserToDocument; - protected virtual void FireBeforeAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) - { - if (BeforeAddMembershipUserToDocument != null) - BeforeAddMembershipUserToDocument(doc, username, e); - } - - public static event AddMembershipUserToDocumentEventHandler AfterAddMembershipUserToDocument; - protected virtual void FireAfterAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) - { - if (AfterAddMembershipUserToDocument != null) - AfterAddMembershipUserToDocument(doc, username, e); - } - } - - public enum ProtectionType - { - NotProtected, - Simple, - Advanced - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Web.Security; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace umbraco.cms.businesslogic.web +{ + /// + /// Summary description for Access. + /// + [Obsolete("Use Umbraco.Core.Service.IPublicAccessService instead")] + public class Access + { + + [Obsolete("Do not access this property directly, it is not thread safe, use GetXmlDocumentCopy instead")] + public static XmlDocument AccessXml + { + get { return GetXmlDocumentCopy(); } + } + + //NOTE: This is here purely for backwards compat + [Obsolete("This should never be used, the data is stored in the database now")] + public static XmlDocument GetXmlDocumentCopy() + { + var allAccessEntries = ApplicationContext.Current.Services.PublicAccessService.GetAll().ToArray(); + + var xml = XDocument.Parse(""); + foreach (var entry in allAccessEntries) + { + var pageXml = new XElement("page", + new XAttribute("id", entry.ProtectedNodeId), + new XAttribute("loginPage", entry.LoginNodeId), + new XAttribute("noRightsPage", entry.NoAccessNodeId)); + + foreach (var rule in entry.Rules) + { + if (rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) + { + //if there is a member id claim then it is 'simple' (this is how legacy worked) + pageXml.Add(new XAttribute("simple", "True")); + pageXml.Add(new XAttribute("memberId", rule.RuleValue)); + } + else if (rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + { + pageXml.Add(new XElement("group", new XAttribute("id", rule.RuleValue))); + } + } + + xml.Root.Add(pageXml); + } + + return xml.ToXmlDocument(); + } + + #region Manipulation methods + + public static void AddMembershipRoleToDocument(int documentId, string role) + { + //event + var doc = new Document(documentId); + var e = new AddMemberShipRoleToDocumentEventArgs(); + new Access().FireBeforeAddMemberShipRoleToDocument(doc, role, e); + + if (e.Cancel) return; + + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + Save(); + + new Access().FireAfterAddMemberShipRoleToDocument(doc, role, e); + } + + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void AddMemberGroupToDocument(int DocumentId, int MemberGroupId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + + if (content == null) + throw new Exception("No content found with document id " + DocumentId); + + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void AddMemberToDocument(int DocumentId, int MemberId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + + if (content == null) + throw new Exception("No content found with document id " + DocumentId); + + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberIdRuleType, + MemberId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + + } + + public static void AddMembershipUserToDocument(int documentId, string membershipUserName) + { + //event + var doc = new Document(documentId); + var e = new AddMembershipUserToDocumentEventArgs(); + new Access().FireBeforeAddMembershipUserToDocument(doc, membershipUserName, e); + + if (e.Cancel) return; + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberUsernameRuleType, + membershipUserName); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + if (entry) + { + Save(); + new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); + } + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void RemoveMemberGroupFromDocument(int DocumentId, int MemberGroupId) + { + var doc = new Document(DocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture)); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + if (entry) + { + Save(); + } + } + + public static void RemoveMembershipRoleFromDocument(int documentId, string role) + { + var doc = new Document(documentId); + var e = new RemoveMemberShipRoleFromDocumentEventArgs(); + new Access().FireBeforeRemoveMemberShipRoleFromDocument(doc, role, e); + + if (e.Cancel) return; + + if (ApplicationContext.Current.Services.PublicAccessService.RemoveRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role)) + { + Save(); + new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); + }; + + } + + public static bool RenameMemberShipRole(string oldRolename, string newRolename) + { + var hasChange = ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(oldRolename, newRolename); + + if (hasChange) + Save(); + + return hasChange; + } + + public static void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) + { + var doc = new Document(DocumentId); + var e = new AddProtectionEventArgs(); + new Access().FireBeforeAddProtection(doc, e); + + if (e.Cancel) return; + + var loginContent = ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId); + if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); + var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); + if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity.Id.ToString()); + if (entry != null) + { + if (Simple) + { + // if using simple mode, make sure that all existing groups are removed + entry.ClearRules(); + } + + //ensure the correct ids are applied + entry.LoginNodeId = loginContent.Id; + entry.NoAccessNodeId = noAccessContent.Id; + } + else + { + entry = new PublicAccessEntry(doc.ContentEntity, + ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId), + ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId), + new List()); + } + + if (ApplicationContext.Current.Services.PublicAccessService.Save(entry)) + { + Save(); + new Access().FireAfterAddProtection(new Document(DocumentId), e); + } + + } + + public static void RemoveProtection(int DocumentId) + { + //event + var doc = new Document(DocumentId); + var e = new RemoveProtectionEventArgs(); + new Access().FireBeforeRemoveProtection(doc, e); + + if (e.Cancel) return; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); + if (entry != null) + { + ApplicationContext.Current.Services.PublicAccessService.Delete(entry); + } + + Save(); + + new Access().FireAfterRemoveProtection(doc, e); + } + #endregion + + #region Reading methods + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static bool IsProtectedByGroup(int DocumentId, int GroupId) + { + var d = new Document(DocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(d.ContentEntity); + if (entry == null) return false; + + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && x.RuleValue == GroupId.ToString(CultureInfo.InvariantCulture)); + + } + + public static bool IsProtectedByMembershipRole(int documentId, string role) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return false; + + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && x.RuleValue == role); + + } + + public static string[] GetAccessingMembershipRoles(int documentId, string path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); + if (entry == null) return new string[] { }; + + var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); + return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static member.MemberGroup[] GetAccessingGroups(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + var memberGroupIdRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType); + + return memberGroupIdRules.Select(x => new member.MemberGroup(int.Parse(x.RuleValue))).ToArray(); + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static member.Member GetAccessingMember(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + //legacy would throw an exception here if it was not 'simple' and simple means based on a member id in this case + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberIdRuleType)) + { + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); + } + + var memberIdRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType); + return new member.Member(int.Parse(memberIdRule.RuleValue)); + + } + + public static MembershipUser GetAccessingMembershipUser(int documentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + //legacy would throw an exception here if it was not 'simple' and simple means based on a username + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) + { + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); + } + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); + return provider.GetUser(usernameRule.RuleValue, false); + + } + + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static bool HasAccess(int DocumentId, member.Member Member) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return true; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return true; + + var memberGroupIds = Member.Groups.Values.Cast().Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && memberGroupIds.Contains(x.RuleValue)); + + } + + [Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)] + public static bool HasAccces(int documentId, object memberId) + { + // Call the correctly named version of this method + return HasAccess(documentId, memberId); + } + + public static bool HasAccess(int documentId, object memberId) + { + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + documentId, + memberId, + ApplicationContext.Current.Services.ContentService, + MembershipProviderExtensions.GetMembersMembershipProvider(), + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); + } + + public static bool HasAccess(int documentId, string path, MembershipUser member) + { + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + path, + member, + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); + } + + public static ProtectionType GetProtectionType(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return ProtectionType.NotProtected; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return ProtectionType.NotProtected; + + //legacy states that if it is protected by a member id then it is 'simple' + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType) + ? ProtectionType.Simple + : ProtectionType.Advanced; + + } + + public static bool IsProtected(int DocumentId, string Path) + { + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); + } + + //return the protection status of this exact document - not based on inheritance + public static bool IsProtected(int DocumentId) + { + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(DocumentId.ToString()); + } + + public static int GetErrorPage(string Path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document); + return entity.Id; + + } + + public static int GetLoginPage(string Path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document); + return entity.Id; + + } + #endregion + + + //NOTE: This is purely here for backwards compat for events + private static void Save() + { + var e = new SaveEventArgs(); + + new Access().FireBeforeSave(e); + + if (e.Cancel) return; + + new Access().FireAfterSave(e); + } + + + //Event delegates + public delegate void SaveEventHandler(Access sender, SaveEventArgs e); + + public delegate void AddProtectionEventHandler(Document sender, AddProtectionEventArgs e); + public delegate void RemoveProtectionEventHandler(Document sender, RemoveProtectionEventArgs e); + + public delegate void AddMemberShipRoleToDocumentEventHandler(Document sender, string role, AddMemberShipRoleToDocumentEventArgs e); + public delegate void RemoveMemberShipRoleFromDocumentEventHandler(Document sender, string role, RemoveMemberShipRoleFromDocumentEventArgs e); + + public delegate void RemoveMemberShipUserFromDocumentEventHandler(Document sender, string MembershipUserName, RemoveMemberShipUserFromDocumentEventArgs e); + public delegate void AddMembershipUserToDocumentEventHandler(Document sender, string MembershipUserName, AddMembershipUserToDocumentEventArgs e); + + //Events + + public static event SaveEventHandler BeforeSave; + protected virtual void FireBeforeSave(SaveEventArgs e) + { + if (BeforeSave != null) + BeforeSave(this, e); + } + + public static event SaveEventHandler AfterSave; + protected virtual void FireAfterSave(SaveEventArgs e) + { + if (AfterSave != null) + AfterSave(this, e); + } + + public static event AddProtectionEventHandler BeforeAddProtection; + protected virtual void FireBeforeAddProtection(Document doc, AddProtectionEventArgs e) + { + if (BeforeAddProtection != null) + BeforeAddProtection(doc, e); + } + + public static event AddProtectionEventHandler AfterAddProtection; + protected virtual void FireAfterAddProtection(Document doc, AddProtectionEventArgs e) + { + if (AfterAddProtection != null) + AfterAddProtection(doc, e); + } + + public static event RemoveProtectionEventHandler BeforeRemoveProtection; + protected virtual void FireBeforeRemoveProtection(Document doc, RemoveProtectionEventArgs e) + { + if (BeforeRemoveProtection != null) + BeforeRemoveProtection(doc, e); + } + + public static event RemoveProtectionEventHandler AfterRemoveProtection; + protected virtual void FireAfterRemoveProtection(Document doc, RemoveProtectionEventArgs e) + { + if (AfterRemoveProtection != null) + AfterRemoveProtection(doc, e); + } + + public static event AddMemberShipRoleToDocumentEventHandler BeforeAddMemberShipRoleToDocument; + protected virtual void FireBeforeAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) + { + if (BeforeAddMemberShipRoleToDocument != null) + BeforeAddMemberShipRoleToDocument(doc, role, e); + } + + public static event AddMemberShipRoleToDocumentEventHandler AfterAddMemberShipRoleToDocument; + protected virtual void FireAfterAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) + { + if (AfterAddMemberShipRoleToDocument != null) + AfterAddMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipRoleFromDocumentEventHandler BeforeRemoveMemberShipRoleToDocument; + protected virtual void FireBeforeRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) + { + if (BeforeRemoveMemberShipRoleToDocument != null) + BeforeRemoveMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipRoleFromDocumentEventHandler AfterRemoveMemberShipRoleToDocument; + protected virtual void FireAfterRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) + { + if (AfterRemoveMemberShipRoleToDocument != null) + AfterRemoveMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipUserFromDocumentEventHandler BeforeRemoveMembershipUserFromDocument; + protected virtual void FireBeforeRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) + { + if (BeforeRemoveMembershipUserFromDocument != null) + BeforeRemoveMembershipUserFromDocument(doc, username, e); + } + + public static event RemoveMemberShipUserFromDocumentEventHandler AfterRemoveMembershipUserFromDocument; + protected virtual void FireAfterRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) + { + if (AfterRemoveMembershipUserFromDocument != null) + AfterRemoveMembershipUserFromDocument(doc, username, e); + } + + public static event AddMembershipUserToDocumentEventHandler BeforeAddMembershipUserToDocument; + protected virtual void FireBeforeAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) + { + if (BeforeAddMembershipUserToDocument != null) + BeforeAddMembershipUserToDocument(doc, username, e); + } + + public static event AddMembershipUserToDocumentEventHandler AfterAddMembershipUserToDocument; + protected virtual void FireAfterAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) + { + if (AfterAddMembershipUserToDocument != null) + AfterAddMembershipUserToDocument(doc, username, e); + } + } + + public enum ProtectionType + { + NotProtected, + Simple, + Advanced + } +} From 3e74bbfb453188f75e6ace3b28558bb31229ce49 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 25 Jun 2019 14:35:28 +1000 Subject: [PATCH 14/35] Creates a custom IAntiForgeryAdditionalDataProvider that provides and validates custom token data in the request which allows having a custom AF token per form created with BeginUmbracoForm --- ...oAntiForgeryAdditionalDataProviderTests.cs | 157 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 11 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 35 +--- ...mbracoAntiForgeryAdditionalDataProvider.cs | 91 ++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoHelper.cs | 44 ++++- src/Umbraco.Web/WebBootManager.cs | 8 +- 8 files changed, 309 insertions(+), 39 deletions(-) create mode 100644 src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs create mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs new file mode 100644 index 0000000000..c81c108e0d --- /dev/null +++ b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs @@ -0,0 +1,157 @@ +using System.Collections.Specialized; +using System.Web; +using System.Web.Helpers; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Mvc; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + [TestFixture] + public class UmbracoAntiForgeryAdditionalDataProviderTests + { + [Test] + public void Test_Wrapped_Non_BeginUmbracoForm() + { + var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom"); + var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var data = provider.GetAdditionalData(httpContextFactory.HttpContext); + + Assert.IsTrue(data.DetectIsJson()); + var json = JsonConvert.DeserializeObject(data); + Assert.AreEqual(null, json.Ufprt); + Assert.IsTrue(json.Stamp != default); + Assert.AreEqual("custom", json.WrappedValue); + } + + [Test] + public void Null_Wrapped_Non_BeginUmbracoForm() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var data = provider.GetAdditionalData(httpContextFactory.HttpContext); + + Assert.IsTrue(data.DetectIsJson()); + var json = JsonConvert.DeserializeObject(data); + Assert.AreEqual(null, json.Ufprt); + Assert.IsTrue(json.Stamp != default); + Assert.AreEqual("default", json.WrappedValue); + } + + [Test] + public void Validate_Non_Json() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello"); + + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_Invalid_Json() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}"); + Assert.IsFalse(isValid); + + } + + [Test] + public void Validate_No_Request_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_No_AdditionalData_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG"); + + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_No_AdditionalData_Or_Request_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); + Assert.IsTrue(isValid); + } + + [Test] + public void Validate_Request_And_AdditionalData_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); + + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsTrue(isValid); + + routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_Wrapped_Request_And_AdditionalData_Ufprt() + { + var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true); + var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); + + var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); + + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsTrue(isValid); + + routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 72939c12d9..7e6fdf4823 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -197,6 +197,7 @@ + diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 90bc97ca19..a64c00a03c 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -306,14 +306,21 @@ namespace Umbraco.Web return; this._disposed = true; + //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. + //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against + //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, + //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique + //per user. + _viewContext.HttpContext.Items["ufprt"] = _encryptedString; + //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() // We have a controllerName and area so we can match if (_controllerName == "UmbRegister" || _controllerName == "UmbProfile" || _controllerName == "UmbLoginStatus" || _controllerName == "UmbLogin") - { - _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + { + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); } //write out the hidden surface form routes diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index bacdd2ffa9..0a5cb06247 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Mvc public class RenderRouteHandler : IRouteHandler { // Define reserved dictionary keys for controller, action and area specified in route additional values data - private static class ReservedAdditionalKeys + internal static class ReservedAdditionalKeys { internal const string Controller = "c"; internal const string Action = "a"; @@ -142,36 +142,7 @@ namespace Umbraco.Web.Mvc return null; } - - string decryptedString; - try - { - decryptedString = encodedVal.DecryptWithMachineKey(); - } - catch (FormatException) - { - LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); - return null; - } - - var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); - var decodedParts = new Dictionary(); - - foreach (var key in parsedQueryString.AllKeys) - { - decodedParts[key] = parsedQueryString[key]; - } - - //validate all required keys exist - - //the controller - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) - return null; - //the action - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) - return null; - //the area - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(encodedVal, out var decodedParts)) return null; foreach (var item in decodedParts.Where(x => new[] { @@ -192,6 +163,8 @@ namespace Umbraco.Web.Mvc }; } + + /// /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that /// the right DataTokens are set. diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs new file mode 100644 index 0000000000..660f7e58fc --- /dev/null +++ b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs @@ -0,0 +1,91 @@ +using System; +using Umbraco.Web.Mvc; +using Umbraco.Core; +using System.Web.Helpers; +using System.Web; +using Newtonsoft.Json; + +namespace Umbraco.Web.Security +{ + /// + /// A custom to create a unique antiforgery token/validator per form created with BeginUmbracoForm + /// + public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider + { + private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; + + /// + /// Constructor, allows wrapping a default provider + /// + /// + public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) + { + _defaultProvider = defaultProvider; + } + + public string GetAdditionalData(HttpContextBase context) + { + return JsonConvert.SerializeObject(new AdditionalData + { + Stamp = DateTime.UtcNow.Ticks, + //this value will be here if this is a BeginUmbracoForms form + Ufprt = context.Items["ufprt"]?.ToString(), + //if there was a wrapped provider, add it's value to the json, else just a static value + WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" + }); + } + + public bool ValidateAdditionalData(HttpContextBase context, string additionalData) + { + if (!additionalData.DetectIsJson()) + return false; //must be json + + AdditionalData json; + try + { + json = JsonConvert.DeserializeObject(additionalData); + } + catch + { + return false; //couldn't parse + } + + if (json.Stamp == default) return false; + + //if there was a wrapped provider, validate it, else validate the static value + var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; + if (!validateWrapped) + return false; + + var ufprtRequest = context.Request["ufprt"]?.ToString(); + + //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate + if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) + return true; + + //if one or the other is null then something is wrong + if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; + if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) + return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) + return false; + + //ensure they all match + return additionalDataParts.Count == requestParts.Count + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; + } + + internal class AdditionalData + { + public string Ufprt { get; set; } + public long Stamp { get; set; } + public string WrappedValue { get; set; } + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 90624df5f2..16dc5dfec5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -307,6 +307,7 @@ + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index f920314075..c00d37f216 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -10,9 +10,11 @@ using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Xml; +using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -1647,6 +1649,43 @@ namespace Umbraco.Web #endregion + internal static bool DecryptAndValidateEncryptedRouteString(string ufprt, out IDictionary parts) + { + string decryptedString; + try + { + decryptedString = ufprt.DecryptWithMachineKey(); + } + catch (FormatException) + { + LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); + parts = null; + return false; + } + + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); + parts = new Dictionary(); + + foreach (var key in parsedQueryString.AllKeys) + { + parts[key] = parsedQueryString[key]; + } + + //validate all required keys exist + + //the controller + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Controller)) + return false; + //the action + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Action)) + return false; + //the area + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Area)) + return false; + + return true; + } + /// /// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which /// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller. @@ -1663,10 +1702,7 @@ namespace Umbraco.Web Mandate.ParameterNotNull(area, "area"); //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - HttpUtility.UrlEncode(controllerName), - HttpUtility.UrlEncode(controllerAction), - area); + var surfaceRouteParams = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode(controllerName)}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode(controllerAction)}&{RenderRouteHandler.ReservedAdditionalKeys.Area}={area}"; //checking if the additional route values is already a dictionary and convert to querystring string additionalRouteValsAsQuery; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 950676355e..ff6fcff164 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -47,10 +47,12 @@ using Umbraco.Web.Profiling; using Umbraco.Web.Search; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; - +using System.Web.Helpers; +using Umbraco.Web.Controllers; namespace Umbraco.Web { + /// /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application /// @@ -188,6 +190,8 @@ namespace Umbraco.Web base.Complete(afterComplete); + AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider); + //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); @@ -281,7 +285,7 @@ namespace Umbraco.Web // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not // utilizing an old path appDomainHash); - + //set the file map and composite file default location to the %temp% location BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder = XmlFileMapper.FileMapDefaultFolder From 3865f4fb50e559f3441574c3facd66191bbc08ca Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:15:06 +0200 Subject: [PATCH 15/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - bugfix - wrong constant used --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index e1716947e1..7d3b15e48b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -131,7 +131,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMediaGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); From 383a8617cebefb77d7d9e74cfc98d152f6bea78b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:19:50 +0200 Subject: [PATCH 16/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fixed test --- .../Services/EntityServiceTests.cs | 1308 ++++++++--------- 1 file changed, 651 insertions(+), 657 deletions(-) diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 8867282155..889d7e5e76 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -1,659 +1,653 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Services -{ - /// - /// Tests covering the EntityService - /// - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)] - [TestFixture, RequiresSTA] - public class EntityServiceTests : BaseServiceTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Children() - { - - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(6)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(4)); - Assert.That(total, Is.EqualTo(10)); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var count = 0; - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - count++; - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - count++; - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(31)); - Assert.That(total, Is.EqualTo(60)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 1, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(29)); - Assert.That(total, Is.EqualTo(60)); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_Including_Recycled() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.ContentService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Document, 0, 1000, out total) - .Select(x => x.Id) - .ToArray(); - - foreach (var c in toDelete) - { - Assert.True(entities.Contains(c.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_Without_Recycled() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.ContentService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Document, 0, 1000, out total, includeTrashed:false) - .Select(x => x.Id) - .ToArray(); - - foreach (var c in toDelete) - { - Assert.IsFalse(entities.Contains(c.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_With_Search() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, "ssss" + Guid.NewGuid(), root); - ServiceContext.ContentService.Save(c1); - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, "tttt" + Guid.NewGuid(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); - Assert.That(entities.Length, Is.EqualTo(10)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); - Assert.That(entities.Length, Is.EqualTo(50)); - Assert.That(total, Is.EqualTo(50)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Children() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 0, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(6)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 1, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(4)); - Assert.That(total, Is.EqualTo(10)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var count = 0; - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - count++; - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - count++; - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(31)); - Assert.That(total, Is.EqualTo(60)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 1, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(29)); - Assert.That(total, Is.EqualTo(60)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_Including_Recycled() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.MediaService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Media, 0, 1000, out total) - .Select(x => x.Id) - .ToArray(); - - foreach (var media in toDelete) - { - Assert.IsTrue(entities.Contains(media.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_Without_Recycled() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.MediaService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Media, 0, 1000, out total, includeTrashed:false) - .Select(x => x.Id) - .ToArray(); - - foreach (var media in toDelete) - { - Assert.IsFalse(entities.Contains(media.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_With_Search() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - c1.Name = "ssss" + Guid.NewGuid(); - ServiceContext.MediaService.Save(c1); - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - c2.Name = "tttt" + Guid.NewGuid(); - ServiceContext.MediaService.Save(c2); - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); - Assert.That(entities.Length, Is.EqualTo(10)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); - Assert.That(entities.Length, Is.EqualTo(50)); - Assert.That(total, Is.EqualTo(50)); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_UmbracoObjectType_Id() - { - var service = ServiceContext.EntityService; - - var objectTypeId = new Guid(Constants.ObjectTypes.Document); - var entities = service.GetAll(objectTypeId).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_Type() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll().ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType() - { - var service = ServiceContext.EntityService; - - var entities = service.GetChildren(-1, UmbracoObjectTypes.Document).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Can_Get_Children_By_ParentId() - { - var service = ServiceContext.EntityService; - - var entities = service.GetChildren(folderId); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(3)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Can_Get_Descendants_By_ParentId() - { - var service = ServiceContext.EntityService; - - var entities = service.GetDescendents(folderId); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Throws_When_Getting_All_With_Invalid_Type() - { - var service = ServiceContext.EntityService; - var objectTypeId = new Guid(Constants.ObjectTypes.ContentItem); - - Assert.Throws(() => service.GetAll()); - Assert.Throws(() => service.GetAll(UmbracoObjectTypes.ContentItem)); - Assert.Throws(() => service.GetAll(objectTypeId)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.DocumentType).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectType_Id() - { - var service = ServiceContext.EntityService; - - var objectTypeId = new Guid(Constants.ObjectTypes.DocumentType); - var entities = service.GetAll(objectTypeId).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_Type() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll().ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_Media_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.Media).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(5)); - - Assert.That( - entities.Any( - x => - x.AdditionalData.Any(y => y.Value is UmbracoEntity.EntityProperty - && ((UmbracoEntity.EntityProperty)y.Value).PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)), Is.True); - } - - [Test] - public void EntityService_Can_Get_ObjectType() - { - var service = ServiceContext.EntityService; - var mediaObjectType = service.GetObjectType(1031); - - Assert.NotNull(mediaObjectType); - Assert.AreEqual(mediaObjectType, UmbracoObjectTypes.MediaType); - } - - [Test] - public void EntityService_Can_Get_Key_For_Id_With_Unknown_Type() - { - var service = ServiceContext.EntityService; - var result = service.GetKeyForId(1060, UmbracoObjectTypes.Unknown); - - Assert.IsTrue(result.Success); - Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering the EntityService + /// + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)] + [TestFixture, RequiresSTA] + public class EntityServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); } - - [Test] - public void EntityService_Can_Get_Key_For_Id() - { - var service = ServiceContext.EntityService; - var result = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); - - Assert.IsTrue(result.Success); - Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); - } - - [Test] - public void EntityService_Cannot_Get_Key_For_Id_With_Incorrect_Object_Type() - { - var service = ServiceContext.EntityService; - var result1 = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); - var result2 = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType); - - Assert.IsTrue(result1.Success); - Assert.IsFalse(result2.Success); - } - - [Test] - public void EntityService_Can_Get_Id_For_Key_With_Unknown_Type() - { - var service = ServiceContext.EntityService; - var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.Unknown); - - Assert.IsTrue(result.Success); - Assert.AreEqual(1060, result.Result); + + [TearDown] + public override void TearDown() + { + base.TearDown(); } - - [Test] - public void EntityService_Can_Get_Id_For_Key() - { - var service = ServiceContext.EntityService; - var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); - - Assert.IsTrue(result.Success); - Assert.AreEqual(1060, result.Result); - } - - [Test] - public void EntityService_Cannot_Get_Id_For_Key_With_Incorrect_Object_Type() - { - var service = ServiceContext.EntityService; - var result1 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); - var result2 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType); - - Assert.IsTrue(result1.Success); - Assert.IsFalse(result2.Success); - } - - [Test] - public void ReserveId() - { - var service = ServiceContext.EntityService; - var guid = Guid.NewGuid(); - - // can reserve - var reservedId = service.ReserveId(guid); - Assert.IsTrue(reservedId > 0); - - // can get it back - var id = service.GetIdForKey(guid, UmbracoObjectTypes.DocumentType); - Assert.IsTrue(id.Success); - Assert.AreEqual(reservedId, id.Result); - - // anything goes - id = service.GetIdForKey(guid, UmbracoObjectTypes.Media); - Assert.IsTrue(id.Success); - Assert.AreEqual(reservedId, id.Result); - - // a random guid won't work - Assert.IsFalse(service.GetIdForKey(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success); - } - - private static bool _isSetup = false; - - private int folderId; - - public override void CreateTestData() - { - if (_isSetup == false) - { - _isSetup = true; - - base.CreateTestData(); - - //Create and Save folder-Media -> 1050 - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder, 0); - folderId = folder.Id; - - //Create and Save image-Media -> 1051 - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); - ServiceContext.MediaService.Save(image, 0); - - //Create and Save file-Media -> 1052 - var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); - var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); - ServiceContext.MediaService.Save(file, 0); - - var subfolder = MockedMedia.CreateMediaFolder(folderMediaType, folder.Id); - ServiceContext.MediaService.Save(subfolder, 0); - var subfolder2 = MockedMedia.CreateMediaFolder(folderMediaType, subfolder.Id); - ServiceContext.MediaService.Save(subfolder2, 0); - - } - - } - } -} \ No newline at end of file + + [Test] + public void EntityService_Can_Get_Paged_Content_Children() + { + + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_Including_Recycled() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.ContentService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Document, 0, 1000, out total) + .Select(x => x.Id) + .ToArray(); + + foreach (var c in toDelete) + { + Assert.True(entities.Contains(c.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_Without_Recycled() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.ContentService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Document, 0, 1000, out total, includeTrashed:false) + .Select(x => x.Id) + .ToArray(); + + foreach (var c in toDelete) + { + Assert.IsFalse(entities.Contains(c.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_With_Search() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, "ssss" + Guid.NewGuid(), root); + ServiceContext.ContentService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, "tttt" + Guid.NewGuid(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Children() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 0, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 1, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_Including_Recycled() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.MediaService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Media, 0, 1000, out total) + .Select(x => x.Id) + .ToArray(); + + foreach (var media in toDelete) + { + Assert.IsTrue(entities.Contains(media.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_Without_Recycled() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.MediaService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Media, 0, 1000, out total, includeTrashed:false) + .Select(x => x.Id) + .ToArray(); + + foreach (var media in toDelete) + { + Assert.IsFalse(entities.Contains(media.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_With_Search() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + c1.Name = "ssss" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + c2.Name = "tttt" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.Document); + var entities = service.GetAll(objectTypeId).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll().ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType() + { + var service = ServiceContext.EntityService; + + var entities = service.GetChildren(-1, UmbracoObjectTypes.Document).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Children_By_ParentId() + { + var service = ServiceContext.EntityService; + + var entities = service.GetChildren(folderId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(3)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Descendants_By_ParentId() + { + var service = ServiceContext.EntityService; + + var entities = service.GetDescendents(folderId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Throws_When_Getting_All_With_Invalid_Type() + { + var service = ServiceContext.EntityService; + var objectTypeId = new Guid(Constants.ObjectTypes.ContentItem); + + Assert.Throws(() => service.GetAll()); + Assert.Throws(() => service.GetAll(UmbracoObjectTypes.ContentItem)); + Assert.Throws(() => service.GetAll(objectTypeId)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.DocumentType).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.DocumentType); + var entities = service.GetAll(objectTypeId).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll().ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_Media_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Media).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(5)); + } + + [Test] + public void EntityService_Can_Get_ObjectType() + { + var service = ServiceContext.EntityService; + var mediaObjectType = service.GetObjectType(1031); + + Assert.NotNull(mediaObjectType); + Assert.AreEqual(mediaObjectType, UmbracoObjectTypes.MediaType); + } + + [Test] + public void EntityService_Can_Get_Key_For_Id_With_Unknown_Type() + { + var service = ServiceContext.EntityService; + var result = service.GetKeyForId(1060, UmbracoObjectTypes.Unknown); + + Assert.IsTrue(result.Success); + Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); + } + + [Test] + public void EntityService_Can_Get_Key_For_Id() + { + var service = ServiceContext.EntityService; + var result = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); + + Assert.IsTrue(result.Success); + Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); + } + + [Test] + public void EntityService_Cannot_Get_Key_For_Id_With_Incorrect_Object_Type() + { + var service = ServiceContext.EntityService; + var result1 = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); + var result2 = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType); + + Assert.IsTrue(result1.Success); + Assert.IsFalse(result2.Success); + } + + [Test] + public void EntityService_Can_Get_Id_For_Key_With_Unknown_Type() + { + var service = ServiceContext.EntityService; + var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.Unknown); + + Assert.IsTrue(result.Success); + Assert.AreEqual(1060, result.Result); + } + + [Test] + public void EntityService_Can_Get_Id_For_Key() + { + var service = ServiceContext.EntityService; + var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); + + Assert.IsTrue(result.Success); + Assert.AreEqual(1060, result.Result); + } + + [Test] + public void EntityService_Cannot_Get_Id_For_Key_With_Incorrect_Object_Type() + { + var service = ServiceContext.EntityService; + var result1 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); + var result2 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType); + + Assert.IsTrue(result1.Success); + Assert.IsFalse(result2.Success); + } + + [Test] + public void ReserveId() + { + var service = ServiceContext.EntityService; + var guid = Guid.NewGuid(); + + // can reserve + var reservedId = service.ReserveId(guid); + Assert.IsTrue(reservedId > 0); + + // can get it back + var id = service.GetIdForKey(guid, UmbracoObjectTypes.DocumentType); + Assert.IsTrue(id.Success); + Assert.AreEqual(reservedId, id.Result); + + // anything goes + id = service.GetIdForKey(guid, UmbracoObjectTypes.Media); + Assert.IsTrue(id.Success); + Assert.AreEqual(reservedId, id.Result); + + // a random guid won't work + Assert.IsFalse(service.GetIdForKey(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success); + } + + private static bool _isSetup = false; + + private int folderId; + + public override void CreateTestData() + { + if (_isSetup == false) + { + _isSetup = true; + + base.CreateTestData(); + + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); + folderId = folder.Id; + + //Create and Save image-Media -> 1051 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> 1052 + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + + var subfolder = MockedMedia.CreateMediaFolder(folderMediaType, folder.Id); + ServiceContext.MediaService.Save(subfolder, 0); + var subfolder2 = MockedMedia.CreateMediaFolder(folderMediaType, subfolder.Id); + ServiceContext.MediaService.Save(subfolder2, 0); + + } + + } + } +} From 0869bf27c741b13cab8876b7dc2ac71b75c42250 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:26:01 +0200 Subject: [PATCH 17/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - clean up --- .../FilterAllowedOutgoingMediaAttribute.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index a246fa26ad..22ce7a5ce7 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -89,42 +89,8 @@ namespace Umbraco.Web.WebApi.Filters { FilterBasedOnStartNode(items, user); } - else - { - // FilterOutPossibleSensitiveData(items); - } - } - /// - /// Removes all properties of the items except the umbracoFile. - /// - /// - private void FilterOutPossibleSensitiveData(IList items) - { - foreach (dynamic item in items) - { - - if (item.Properties != null) - { - if (item.Properties is IList properties) - // Iterate reverse, because we removed from the same list, such that the ordering of indexes not - // changes doing the iteration - for (var i = properties.Count - 1; i >= 0; i--) - { - dynamic property = properties[i]; - if (property.Alias != null && property.Alias is string) - { - var alias = property.Alias as string; - if (string.Equals(alias, Constants.Conventions.Media.File) == false) - { - properties.RemoveAt(i); - } - } - } - } - } - } internal void FilterBasedOnStartNode(IList items, IUser user) { From afac4bb918a578d8b5513cd5cac720dde6e2089d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:18:30 +0200 Subject: [PATCH 18/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - missing entityResource injection --- .../src/views/propertyeditors/grid/editors/rte.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 30ea1eaef6..601c0a36d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -1,7 +1,7 @@ (function() { "use strict"; - function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) { + function GridRichTextEditorController($scope, tinyMceService, macroService, editorState, entityResource) { var vm = this; From d558646d75a45b8cd52c71d21bdd92644e1e3f58 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:21:51 +0200 Subject: [PATCH 19/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index cf49e892dd..3e5acd0296 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -310,9 +310,13 @@ namespace Umbraco.Core.Services { if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) { - var value = contentProperty.Value.ToString(); + var value = contentProperty.Value?.ToString(); + + if (string.IsNullOrEmpty(value)) + { + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } - result.AddRange(GetAnchorValuesFromRTEContent(value)); } } From 4dd325cec943045847852fde3e28a74da37ae1f6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:21:51 +0200 Subject: [PATCH 20/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index cf49e892dd..abde058252 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -310,9 +310,13 @@ namespace Umbraco.Core.Services { if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) { - var value = contentProperty.Value.ToString(); + var value = contentProperty.Value?.ToString(); + + if (!string.IsNullOrEmpty(value)) + { + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } - result.AddRange(GetAnchorValuesFromRTEContent(value)); } } From 36559b876123bf9521b6f5f93dd48bd6727659b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:22:56 +0200 Subject: [PATCH 21/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3e5acd0296..abde058252 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -312,7 +312,7 @@ namespace Umbraco.Core.Services { var value = contentProperty.Value?.ToString(); - if (string.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { result.AddRange(GetAnchorValuesFromRTEContent(value)); } From 1994194656c419359fc827cfc030818612bf9a20 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 11:00:20 +0200 Subject: [PATCH 22/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Review fixes --- src/Umbraco.Core/Models/DataTypeDefinition.cs | 41 - .../Models/DataTypeDefinitionExtensions.cs | 47 + .../Models/IDataTypeDefinition.cs | 5 - .../Factories/DataTypeDefinitionFactory.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 12 +- src/Umbraco.Core/Services/DataTypeService.cs | 16 - .../Services/DateTypeServiceExtensions.cs | 22 + src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IDataTypeService.cs | 1 - src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../src/common/resources/content.resource.js | 1045 ++++++++--------- .../src/common/resources/entity.resource.js | 32 +- .../src/common/resources/media.resource.js | 766 ++++++------ .../src/common/services/search.service.js | 20 +- .../common/dialogs/linkpicker.controller.js | 3 +- .../common/dialogs/mediapicker.controller.js | 2 +- .../common/dialogs/treepicker.controller.js | 451 +++---- .../linkpicker/linkpicker.controller.js | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/EntityController.cs | 48 +- src/Umbraco.Web/Editors/MediaController.cs | 49 +- .../Models/ContentEditing/UrlAndAnchors.cs | 4 +- .../Mapping/ContentPropertyBasicConverter.cs | 16 +- .../ContentPropertyDisplayConverter.cs | 134 +-- .../Mapping/ContentPropertyDtoConverter.cs | 82 +- .../Mapping/ContentPropertyModelMapper.cs | 74 +- .../Models/Mapping/PreValueDisplayResolver.cs | 4 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 27 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 1 + ...EnsureUserPermissionForContentAttribute.cs | 22 +- .../FilterAllowedOutgoingMediaAttribute.cs | 16 +- 31 files changed, 1417 insertions(+), 1545 deletions(-) create mode 100644 src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs create mode 100644 src/Umbraco.Core/Services/DateTypeServiceExtensions.cs diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 5e1fd649ae..997dd6b16f 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -32,35 +32,6 @@ namespace Umbraco.Core.Models private string _propertyEditorAlias; private DataTypeDatabaseType _databaseType; - private static readonly ISet IdsOfBuildInDataTypes = new HashSet() - { - Constants.DataTypes.ContentPickerGuid, - Constants.DataTypes.MemberPickerGuid, - Constants.DataTypes.MediaPickerGuid, - Constants.DataTypes.MultipleMediaPickerGuid, - Constants.DataTypes.RelatedLinksGuid, - Constants.DataTypes.MemberGuid, - Constants.DataTypes.ImageCropperGuid, - Constants.DataTypes.TagsGuid, - Constants.DataTypes.ListViewContentGuid, - Constants.DataTypes.ListViewMediaGuid, - Constants.DataTypes.ListViewMembersGuid, - Constants.DataTypes.DatePickerWithTimeGuid, - Constants.DataTypes.ApprovedColorGuid, - Constants.DataTypes.DropdownMultipleGuid, - Constants.DataTypes.RadioboxGuid, - Constants.DataTypes.DatePickerGuid, - Constants.DataTypes.DropdownGuid, - Constants.DataTypes.CheckboxListGuid, - Constants.DataTypes.CheckboxGuid, - Constants.DataTypes.NumericGuid, - Constants.DataTypes.RichtextEditorGuid, - Constants.DataTypes.TextstringGuid, - Constants.DataTypes.TextareaGuid, - Constants.DataTypes.UploadGuid, - Constants.DataTypes.LabelGuid, - }; - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] [EditorBrowsable(EditorBrowsableState.Never)] public DataTypeDefinition(int parentId, Guid controlId) @@ -94,15 +65,6 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - public DataTypeDefinition(string propertyEditorAlias, Guid uniqueId) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - IsBuildInDataType = IdsOfBuildInDataTypes.Contains(uniqueId); - } - private static readonly Lazy Ps = new Lazy(); private class PropertySelectors @@ -253,8 +215,5 @@ namespace Umbraco.Core.Models { get { return _additionalData; } } - - public bool IsBuildInDataType { get;} } - } diff --git a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs b/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs new file mode 100644 index 0000000000..45c4420820 --- /dev/null +++ b/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + public static class DataTypeDefinitionExtensions + { + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + /// + /// Returns true if this date type is build-in/default. + /// + /// The data type definition. + /// + public static bool IsBuildInDataType(this IDataTypeDefinition dataTypeDefinition) + { + return IdsOfBuildInDataTypes.Contains(dataTypeDefinition.Key); + } + } +} diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index f57a1bdca7..0162fe7738 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -20,10 +20,5 @@ namespace Umbraco.Core.Models /// Gets or Sets the DatabaseType for which the DataType's value is saved as /// DataTypeDatabaseType DatabaseType { get; set; } - - /// - /// Gets information about whether this data type is build in or not. - /// - bool IsBuildInDataType { get;} } } diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 14c162d8ea..71248a1ce8 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Factories public IDataTypeDefinition BuildEntity(DataTypeDto dto) { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias,dto.NodeDto.UniqueId); + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); try diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index abde058252..2994bcc4f6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Services //Support recursive locks because some of the methods that require locking call other methods that require locking. //for example, the Move method needs to be locked but this calls the Save method which also needs to be locked. private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); public ContentService( IDatabaseUnitOfWorkProvider provider, @@ -300,7 +301,7 @@ namespace Umbraco.Core.Services return content; } - public IList GetAnchorValuesFromRTEs(int id) + public IEnumerable GetAnchorValuesFromRTEs(int id) { var result = new List(); @@ -308,7 +309,7 @@ namespace Umbraco.Core.Services foreach (var contentProperty in content.Properties) { - if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) + if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.TinyMCEAlias)) { var value = contentProperty.Value?.ToString(); @@ -324,11 +325,12 @@ namespace Umbraco.Core.Services return result; } - public IList GetAnchorValuesFromRTEContent(string rteContent) + + public IEnumerable GetAnchorValuesFromRTEContent(string rteContent) { var result = new List(); - var regex = new Regex(""); - var matches = regex.Matches(rteContent); + + var matches = AnchorRegex.Matches(rteContent); foreach (Match match in matches) { diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index b02fb644d8..cc9e99d39f 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -349,22 +349,6 @@ namespace Umbraco.Core.Services new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } - public bool IsDataTypeIgnoringUserStartNodes(Guid key) - { - var dataType = GetDataTypeDefinitionById(key); - - if (dataType != null) - { - var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) - { - return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); - } - } - - return false; - } - /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs new file mode 100644 index 0000000000..63006653ab --- /dev/null +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Umbraco.Core.Services +{ + public static class DateTypeServiceExtensions + { + public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) + { + var dataType = dataTypeService.GetDataTypeDefinitionById(key); + + if (dataType != null) + { + var preValues = dataTypeService.GetPreValuesCollectionByDataTypeId(dataType.Id); + if (preValues.PreValuesAsDictionary.TryGetValue( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) + return preValue.Value.InvariantEquals("1"); + } + + return false; + } + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 8c063890fd..021b483ec2 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -714,7 +714,7 @@ namespace Umbraco.Core.Services /// IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); - IList GetAnchorValuesFromRTEs(int id); - IList GetAnchorValuesFromRTEContent(string rteContent); + IEnumerable GetAnchorValuesFromRTEs(int id); + IEnumerable GetAnchorValuesFromRTEContent(string rteContent); } } diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index de8ee5f49c..7410268ae4 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -163,6 +163,5 @@ namespace Umbraco.Core.Services Attempt> Move(IDataTypeDefinition toMove, int parentId); - bool IsDataTypeIgnoringUserStartNodes(Guid dataTypeId); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f29c1bea46..49f5e8b5d2 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -377,6 +377,7 @@ + @@ -767,6 +768,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index d85cc74d7d..fe47c753fa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -1,34 +1,34 @@ /** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
-  *          });
-  * 
- **/ + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
+ *    contentResource.getById(1234)
+ *          .then(function(data) {
+ *              $scope.content = data;
+ *          });
+ * 
+ **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { + function saveContentItem(content, action, files, restApiUrl) { return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, + restApiUrl: restApiUrl, content: content, action: action, files: files, @@ -41,55 +41,55 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return { - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * contentResource.sort({ parentId: 1244, sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+         * var ids = [123,34533,2334,23434];
+         * contentResource.sort({ parentId: 1244, sortedIds: ids })
+         *    .then(function() {
+         *        $scope.complete = true;
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -102,37 +102,37 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * contentResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
-          *      alert("node didnt move:" + err.data.Message);
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * contentResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -145,38 +145,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
-          * contentResource.copy({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was copied");
-          *    }, function(err){
-          *      alert("node wasnt copy:" + err.data.Message);
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+         * contentResource.copy({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was copied");
+         *    }, function(err){
+         *      alert("node wasnt copy:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ copy: function (args) { if (!args) { throw "args cannot be null"; @@ -189,169 +189,152 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
-          * contentResource.unPublish(1234)
-          *    .then(function() {
-          *        alert("node was unpulished");
-          *    }, function(err){
-          *      alert("node wasnt unpublished:" + err.data.Message);
-          *    });
-          * 
- * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
+         * contentResource.unPublish(1234)
+         *    .then(function() {
+         *        alert("node was unpulished");
+         *    }, function(err){
+         *      alert("node wasnt unpublished:" + err.data.Message);
+         *    });
+         * 
+ * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ unPublish: function (id) { if (!id) { throw "id cannot be null"; } return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
-          * contentResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
-          * 
- * - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
+         * contentResource.emptyRecycleBin()
+         *    .then(function() {
+         *        alert('its empty!');
+         *    });
+         * 
+ * + * @returns {Promise} resourcePromise object. + * + */ emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
-          * contentResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
-          * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
+         * contentResource.deleteById(1234)
+         *    .then(function() {
+         *        alert('its gone!');
+         *    });
+         * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + id); + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *        var myDoc = content;
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id id of content item to return - * @param {Object} options optional options object - * @param {Guid} options.dataTypeId set to determine whether to allow a user to choose nodes that they normally don't have access to - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id, options) { - var defaults = { - dataTypeId: null - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - var args = [{ id: id }]; - if(options.dataTypeId){ - args.push({ dataTypeId: options.dataTypeId }); - } + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *        var myDoc = content;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - args)), - 'Failed to retrieve data for content id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); }, getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); }, getNotifySettingsById: function (id) { @@ -378,26 +361,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
-          * contentResource.getByIds( [1234,2526,28262])
-          *    .then(function(contentArray) {
-          *        var myDoc = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
+         * contentResource.getByIds( [1234,2526,28262])
+         *    .then(function(contentArray) {
+         *        var myDoc = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ getByIds: function (ids) { var idQuery = ""; @@ -406,125 +389,125 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
-          * contentResource.getScaffold(1234, 'homepage')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
-          *        myDoc.name = "My new document";
-          *
-          *        contentResource.publish(myDoc, true)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
-          * 
- * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+         * contentResource.getScaffold(1234, 'homepage')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
+         *        myDoc.name = "My new document";
+         *
+         *        contentResource.publish(myDoc, true)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
+         * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); }, getBlueprintScaffold: function (parentId, blueprintId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId}])), - 'Failed to retrieve blueprint for id ' + blueprintId); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), + 'Failed to retrieve blueprint for id ' + blueprintId); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
-          * contentResource.getNiceUrl(id)
-          *    .then(function(url) {
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
+         * contentResource.getNiceUrl(id)
+         *    .then(function(url) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ getNiceUrl: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
-          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
+         * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+         *    .then(function(contentArray) {
+         *        var children = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -584,44 +567,44 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
-          * contentResource.hasPermission('p',1234)
-          *    .then(function() {
-          *        alert('You are allowed to publish this item');
-          *    });
-          * 
- * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
+         * contentResource.hasPermission('p',1234)
+         *    .then(function() {
+         *        alert('You are allowed to publish this item');
+         *    });
+         * 
+ * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ checkPermission: function (permission, id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); }, getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); }, getPermissions: function (nodeIds) { @@ -635,136 +618,136 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name!";
-          *          contentResource.save(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name!";
+         *          contentResource.save(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
+         * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ save: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); }, saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.publish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and published again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name, and be published!";
+         *          contentResource.publish(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
+         * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
-          * contentResource.getById(1234)
-          *    .then(function(content) {
-          *          content.name = "I want a new name, and be published!";
-          *          contentResource.sendToPublish(content, false)
-          *            .then(function(content){
-          *                alert("Retrieved, updated and notication send off");
-          *            });
-          *    });
-          * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
+         * contentResource.getById(1234)
+         *    .then(function(content) {
+         *          content.name = "I want a new name, and be published!";
+         *          contentResource.sendToPublish(content, false)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and notication send off");
+         *            });
+         *    });
+         * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ sendToPublish: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-          * contentResource.publishById(1234)
-          *    .then(function(content) {
-          *        alert("published");
-          *    });
-          * 
- * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
+         * contentResource.publishById(1234)
+         *    .then(function(content) {
+         *        alert("published");
+         *    });
+         * 
+ * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ publishById: function (id) { if (!id) { @@ -772,12 +755,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index db5e9dd404..416fd0a286 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -172,14 +172,14 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrlAndAnchors", - [{ id: id }])), + { id: id })), 'Failed to retrieve url and anchors data for id ' + id); }, getAnchors: function (rteContent) { - if (rteContent == null || rteContent.length === 0) { + if (!rteContent || rteContent.length === 0) { return []; } @@ -188,7 +188,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAnchors", - [{ rteContent: rteContent }])), + { rteContent: rteContent })), 'Failed to anchors data for rte content ' + rteContent); }, @@ -543,19 +543,16 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, options, canceler) { + search: function (query, type, searchFrom, canceler, dataTypeId) { - var defaults = { - searchFrom: null, - dataTypeId: null - }; - if (options === undefined) { - options = {}; + var args = [{ query: query }, { type: type }]; + if (searchFrom) { + args.push({ searchFrom: searchFrom }); + } + + if (dataTypeId) { + args.push({ dataTypeId: dataTypeId }); } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; var httpConfig = {}; if (canceler) { @@ -567,12 +564,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "Search", - { - query: query, - type: type, - searchFrom: options.searchFrom, - dataTypeId: options.dataTypeId - }), + args), httpConfig), 'Failed to retrieve entity data for query ' + query); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 6600aec0c7..ab5b88f841 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -1,16 +1,16 @@ /** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ + * @ngdoc service + * @name umbraco.resources.mediaResource + * @description Loads in data for media + **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ function saveMediaItem(content, action, files) { return umbRequestHelper.postSaveContent({ restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), + "mediaApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -24,35 +24,35 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-          * var ids = [123,34533,2334,23434];
-          * mediaResource.sort({ sortedIds: ids })
-          *    .then(function() {
-          *        $scope.complete = true;
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+         * var ids = [123,34533,2334,23434];
+         * mediaResource.sort({ sortedIds: ids })
+         *    .then(function() {
+         *        $scope.complete = true;
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ sort: function (args) { if (!args) { throw "args cannot be null"; @@ -65,37 +65,37 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-          * mediaResource.move({ parentId: 1244, id: 123 })
-          *    .then(function() {
-          *        alert("node was moved");
-          *    }, function(err){
-          *      alert("node didnt move:" + err.data.Message);
-          *    });
-          * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * mediaResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ move: function (args) { if (!args) { throw "args cannot be null"; @@ -108,121 +108,121 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; } } + } - return { - errorMsg: errorMsg - }; - } - }); + return { + errorMsg: errorMsg + }; + } + }); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *        var myMedia = media;
-          *        alert('its here!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
+         * mediaResource.getById(1234)
+         *    .then(function(media) {
+         *        var myMedia = media;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ getById: function (id) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
-          * mediaResource.deleteById(1234)
-          *    .then(function() {
-          *        alert('its gone!');
-          *    });
-          * 
- * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
+         * mediaResource.deleteById(1234)
+         *    .then(function() {
+         *        alert('its gone!');
+         *    });
+         * 
+ * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
-          * mediaResource.getByIds( [1234,2526,28262])
-          *    .then(function(mediaArray) {
-          *        var myDoc = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
+         * mediaResource.getByIds( [1234,2526,28262])
+         *    .then(function(mediaArray) {
+         *        var myDoc = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ getByIds: function (ids) { var idQuery = ""; @@ -231,96 +231,96 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
-          * mediaResource.getScaffold(1234, 'folder')
-          *    .then(function(scaffold) {
-          *        var myDoc = scaffold;
-          *        myDoc.name = "My new media item";
-          *
-          *        mediaResource.save(myDoc, true)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
+         * mediaResource.getScaffold(1234, 'folder')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
+         *        myDoc.name = "My new media item";
+         *
+         *        mediaResource.save(myDoc, true)
+         *            .then(function(media){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
+         * 
+ * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); }, rootMedia: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
-          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray;
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
+         * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+         *    .then(function(contentArray) {
+         *        var children = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -329,8 +329,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true, - dataTypeId: null + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -362,111 +361,110 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter }, - { dataTypeId: options.dataTypeId } - ])), - 'Failed to retrieve children for media item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-          * mediaResource.getById(1234)
-          *    .then(function(media) {
-          *          media.name = "I want a new name!";
-          *          mediaResource.save(media, false)
-          *            .then(function(media){
-          *                alert("Retrieved, updated and saved again");
-          *            });
-          *    });
-          * 
- * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+         * mediaResource.getById(1234)
+         *    .then(function(media) {
+         *          media.name = "I want a new name!";
+         *          mediaResource.save(media, false)
+         *            .then(function(media){
+         *                alert("Retrieved, updated and saved again");
+         *            });
+         *    });
+         * 
+ * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ save: function (media, isNew, files) { return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
-          * mediaResource.addFolder("My gallery", 1234)
-          *    .then(function(folder) {
-          *        alert('New folder');
-          *    });
-          * 
- * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
+         * mediaResource.addFolder("My gallery", 1234)
+         *    .then(function(folder) {
+         *        alert('New folder');
+         *    });
+         * 
+ * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ addFolder: function (name, parentId) { return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##usage - *
-          * mediaResource.getChildFolders(1234)
-          *    .then(function(data) {
-          *        alert('folders');
-          *    });
-          * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##usage + *
+         * mediaResource.getChildFolders(1234)
+         *    .then(function(data) {
+         *        alert('folders');
+         *    });
+         * 
+ * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ getChildFolders: function (parentId) { if (!parentId) { parentId = -1; @@ -474,67 +472,67 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { //NOTE: This will return a max of 500 folders, if more is required it needs to be paged return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { - id: parentId - })), - 'Failed to retrieve child folders for media item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { + id: parentId + })), + 'Failed to retrieve child folders for media item ' + parentId); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
-          * mediaResource.emptyRecycleBin()
-          *    .then(function() {
-          *        alert('its empty!');
-          *    });
-          * 
- * - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
+         * mediaResource.emptyRecycleBin()
+         *    .then(function() {
+         *        alert('its empty!');
+         *    });
+         * 
+ * + * @returns {Promise} resourcePromise object. + * + */ emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+         * mediaResource.search("my search", 1, 100, -1)
+         *    .then(function(searchResult) {
+         *        alert('it's here!');
+         *    });
+         * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ search: function (query, pageNumber, pageSize, searchFrom) { var args = [ @@ -545,12 +543,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { ]; return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 24b946cdc8..e4f527f2d3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,11 +42,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { + return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -71,12 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - dataTypeId: args.dataTypeId - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -101,12 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - dataTypeId: args.dataTypeId - } - - return entityResource.search(args.term, "Media", options).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index b1fa038c44..e08178ec93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -8,7 +8,6 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", searchText = value + "..."; }); - debugger; $scope.dialogTreeEventHandler = $({}); $scope.target = {}; $scope.searchInfo = { @@ -134,7 +133,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", $scope.target.id = media.id; $scope.target.isMedia = true; $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); + $scope.target.url = media.image; } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index da7ca307ca..a0de457b87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -83,7 +83,7 @@ angular.module("umbraco") entityResource.getChildren(folder.id, "Media") .then(function(data) { for (i=0;i= 0; + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; - /** method to select a search result */ - $scope.selectResult = function (evt, result) { + /** method to select a search result */ + $scope.selectResult = function (evt, result) { if (result.filtered) { return; } - result.selected = result.selected === true ? false : true; + result.selected = result.selected === true ? false : true; - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); - //add/remove to our custom tracked list of selected search results + //add/remove to our custom tracked list of selected search results if (result.selected) { $scope.searchInfo.selectedSearchResults.push(result); } @@ -306,23 +306,23 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", }); } - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); if (found) { found.selected = result.selected; } - } + } - }; + }; - $scope.hideSearch = function () { + $scope.hideSearch = function () { //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { + if (tree) { - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! function checkChildren(children) { _.each(children, function (child) { //check if the id is in the selection, if so ensure it's flagged as selected @@ -385,7 +385,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", }); } checkChildren(tree.root.children); - } + } $scope.searchInfo.showSearch = false; @@ -394,37 +394,38 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", $scope.searchInfo.results = []; } - $scope.onSearchResults = function(results) {; + $scope.onSearchResults = function(results) { + //filter all items - this will mark an item as filtered - performFiltering(results); + performFiltering(results); - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); - $scope.searchInfo.results = results; + $scope.searchInfo.results = results; //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); - $scope.searchInfo.showSearch = true; - }; + $scope.searchInfo.showSearch = true; + }; - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 787fe4186e..2b6b77ed41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -128,7 +128,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.model.target.udi = media.udi; $scope.model.target.isMedia = true; $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.url = media.image; $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c909c1eb84..1ac8318f9c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -262,11 +262,10 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id ///
/// - /// If set used to lookup whether the user and group start node permissions should be ignored. /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, [FromUri]Guid? dataTypeId = null) + public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -1117,7 +1116,6 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against - /// If set to true, user and group start node permissions will be ignored. /// internal static bool CheckPermissions( IDictionary storage, @@ -1127,8 +1125,7 @@ namespace Umbraco.Web.Editors IEntityService entityService, int nodeId, char[] permissionsToCheck = null, - IContent contentItem = null, - bool ignoreUserStartNodes = false) + IContent contentItem = null) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -1149,11 +1146,6 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - if(ignoreUserStartNodes) - { - return true; - } - var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index d6e997b57e..6c9cc7c36b 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -20,6 +20,7 @@ using System.Text.RegularExpressions; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Search; using Umbraco.Web.Trees; @@ -74,22 +75,6 @@ namespace Umbraco.Web.Editors return returnObj; } - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, null, searchFrom); - } - /// /// Searches for results based on the entity type /// @@ -101,7 +86,7 @@ namespace Umbraco.Web.Editors /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, Guid? dataTypeId, string searchFrom = null) + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) { //TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -301,7 +286,7 @@ namespace Umbraco.Web.Editors } [HttpPost] - public IList GetAnchors(string rteContent) + public IEnumerable GetAnchors(string rteContent) { var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); @@ -565,29 +550,16 @@ namespace Umbraco.Web.Editors } } - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - null, orderBy, orderDirection, filter); - } public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, int pageNumber, int pageSize, - Guid? dataTypeId, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -654,13 +626,7 @@ namespace Umbraco.Web.Editors private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) - { - return GetResultForAncestors(id, type, null); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId) + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) { return GetResultForAncestors(id, type, dataTypeId); } @@ -681,7 +647,7 @@ namespace Umbraco.Web.Editors private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom, ignoreUserStartNodes); } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 788ae3a0c7..54aaacf2f2 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -264,28 +264,23 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "", - Guid? dataTypeId = null) + string filter = "") { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { - var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - if (ignoreUserStartNodes == false) + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) { - if (pageNumber > 0) - return new PagedResult>(0, 0, 0); - var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - return new PagedResult>(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(Mapper.Map>) - }; - return pr; - } + Items = nodes.Select(Mapper.Map>) + }; + return pr; } // else proceed as usual @@ -317,7 +312,7 @@ namespace Umbraco.Web.Editors } /// - /// This method is obsolete, use the overload with dataTypeId instead + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity GUID id /// /// @@ -328,7 +323,7 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, int pageNumber = 0, @@ -338,7 +333,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } /// @@ -351,11 +346,10 @@ namespace Umbraco.Web.Editors /// /// /// - /// If set used to lookup whether user and group start node permissions should be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, - Guid? dataTypeId, + bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -366,13 +360,13 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } throw new HttpResponseException(HttpStatusCode.NotFound); } /// - /// This method is obsolete, use the overload with dataTypeId instead + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity UDI id /// /// @@ -383,7 +377,7 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, int pageNumber = 0, @@ -393,7 +387,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } /// @@ -406,11 +400,10 @@ namespace Umbraco.Web.Editors /// /// /// - /// If set used to lookup whether the user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, - Guid? dataTypeId, + bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -424,7 +417,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs index a8cf2e26ee..86642a8d65 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "urlAndAnchors", Namespace = "")] public class UrlAndAnchors { - public UrlAndAnchors(string url, IList anchorValues) + public UrlAndAnchors(string url, IEnumerable anchorValues) { Url = url; AnchorValues = anchorValues; @@ -16,6 +16,6 @@ namespace Umbraco.Web.Models.ContentEditing public string Url { get; } [DataMember(Name = "anchorValues")] - public IList AnchorValues { get; } + public IEnumerable AnchorValues { get; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index 02cf570f7d..dfff72a6f1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -18,13 +18,15 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : ITypeConverter where T : ContentPropertyBasic, new() { - protected IDataTypeService DataTypeService { get; private set; } + protected IDataTypeService DataTypeService { get; } + protected IEntityService EntityService { get; } private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService, IEntityService entityService) { DataTypeService = dataTypeService; + EntityService = entityService; } /// @@ -48,12 +50,18 @@ namespace Umbraco.Web.Models.Mapping editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); } - var dataTypeDefinition = DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); + var dataTypeDefinitionKey = EntityService.GetKeyForId(property.PropertyType.DataTypeDefinitionId, UmbracoObjectTypes.DataType); + + if (dataTypeDefinitionKey.Success == false) + { + throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeDefinitionId); + } + var result = new T { Id = property.Id, - DataTypeId = dataTypeDefinition.Key, + DataTypeId = dataTypeDefinitionKey.Result, Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 86c79d9c59..9c9cad560c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -1,67 +1,67 @@ -using System; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a ContentPropertyDisplay from a Property - /// - internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter - { - private readonly ILocalizedTextService _textService; - - public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService) - : base(dataTypeService) - { - _textService = textService; - } - public override ContentPropertyDisplay Convert(ResolutionContext context) - { - var display = base.Convert(context); - - var originalProperty = context.SourceValue as Property; - if (originalProperty == null) - throw new InvalidOperationException("Source value is not a property."); - - var dataTypeService = DataTypeService; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); - - //configure the editor for display with the pre-values - var valEditor = display.PropertyEditor.ValueEditor; - valEditor.ConfigureForDisplay(preVals); - - //set the display properties after mapping - display.Alias = originalProperty.Alias; - display.Description = originalProperty.PropertyType.Description; - display.Label = originalProperty.PropertyType.Name; - display.HideLabel = valEditor.HideLabel; - - //add the validation information - display.Validation.Mandatory = originalProperty.PropertyType.Mandatory; - display.Validation.Pattern = originalProperty.PropertyType.ValidationRegExp; - - if (display.PropertyEditor == null) - { - //display.Config = PreValueCollection.AsDictionary(preVals); - //if there is no property editor it means that it is a legacy data type - // we cannot support editing with that so we'll just render the readonly value view. - display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; - } - else - { - //let the property editor format the pre-values - display.Config = display.PropertyEditor.PreValueEditor.ConvertDbToEditor(display.PropertyEditor.DefaultPreValues, preVals); - display.View = valEditor.View; - } - - //Translate - display.Label = _textService.UmbracoDictionaryTranslate(display.Label); - display.Description = _textService.UmbracoDictionaryTranslate(display.Description); - - return display; - } - } -} +using System; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDisplay from a Property + /// + internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter + { + private readonly ILocalizedTextService _textService; + + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, IEntityService entityService) + : base(dataTypeService, entityService) + { + _textService = textService; + } + public override ContentPropertyDisplay Convert(ResolutionContext context) + { + var display = base.Convert(context); + + var originalProperty = context.SourceValue as Property; + if (originalProperty == null) + throw new InvalidOperationException("Source value is not a property."); + + var dataTypeService = DataTypeService; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); + + //configure the editor for display with the pre-values + var valEditor = display.PropertyEditor.ValueEditor; + valEditor.ConfigureForDisplay(preVals); + + //set the display properties after mapping + display.Alias = originalProperty.Alias; + display.Description = originalProperty.PropertyType.Description; + display.Label = originalProperty.PropertyType.Name; + display.HideLabel = valEditor.HideLabel; + + //add the validation information + display.Validation.Mandatory = originalProperty.PropertyType.Mandatory; + display.Validation.Pattern = originalProperty.PropertyType.ValidationRegExp; + + if (display.PropertyEditor == null) + { + //display.Config = PreValueCollection.AsDictionary(preVals); + //if there is no property editor it means that it is a legacy data type + // we cannot support editing with that so we'll just render the readonly value view. + display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; + } + else + { + //let the property editor format the pre-values + display.Config = display.PropertyEditor.PreValueEditor.ConvertDbToEditor(display.PropertyEditor.DefaultPreValues, preVals); + display.View = valEditor.View; + } + + //Translate + display.Label = _textService.UmbracoDictionaryTranslate(display.Label); + display.Description = _textService.UmbracoDictionaryTranslate(display.Description); + + return display; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 61fd4fbe00..03b2fc7bcd 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -1,41 +1,41 @@ -using System; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a ContentPropertyDto from a Property - /// - internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter - { - public ContentPropertyDtoConverter(IDataTypeService dataTypeService) - : base(dataTypeService) - { - } - - public override ContentPropertyDto Convert(ResolutionContext context) - { - var propertyDto = base.Convert(context); - - var originalProperty = context.SourceValue as Property; - if (originalProperty == null) - throw new InvalidOperationException("Source value is not a property."); - - var dataTypeService = DataTypeService; - - propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; - propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; - propertyDto.Description = originalProperty.PropertyType.Description; - propertyDto.Label = originalProperty.PropertyType.Name; - - //TODO: We should be able to look both of these up at the same time! - propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); - propertyDto.PreValues = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); - - return propertyDto; - } - } -} +using System; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDto from a Property + /// + internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter + { + public ContentPropertyDtoConverter(IDataTypeService dataTypeService, IEntityService entityService) + : base(dataTypeService, entityService) + { + } + + public override ContentPropertyDto Convert(ResolutionContext context) + { + var propertyDto = base.Convert(context); + + var originalProperty = context.SourceValue as Property; + if (originalProperty == null) + throw new InvalidOperationException("Source value is not a property."); + + var dataTypeService = DataTypeService; + + propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; + propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; + propertyDto.Description = originalProperty.PropertyType.Description; + propertyDto.Label = originalProperty.PropertyType.Name; + + //TODO: We should be able to look both of these up at the same time! + propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); + propertyDto.PreValues = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); + + return propertyDto; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs index f401832020..fd9d3d19f8 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -1,37 +1,37 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is - /// why they are in their own mapper - /// - internal class ContentPropertyModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM Property TO ContentPropertyBasic - config.CreateMap>() - .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) - .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) - .ForMember(tab => tab.Properties, expression => expression.Ignore()) - .ForMember(tab => tab.Alias, expression => expression.Ignore()); - - //FROM Property TO ContentPropertyBasic - config.CreateMap() - .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService)); - - //FROM Property TO ContentPropertyDto - config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService)); - - //FROM Property TO ContentPropertyDisplay - config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService)); - } - } -} +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Mapping; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is + /// why they are in their own mapper + /// + internal class ContentPropertyModelMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) + { + //FROM Property TO ContentPropertyBasic + config.CreateMap>() + .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) + .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) + .ForMember(tab => tab.Properties, expression => expression.Ignore()) + .ForMember(tab => tab.Alias, expression => expression.Ignore()); + + //FROM Property TO ContentPropertyBasic + config.CreateMap() + .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService, applicationContext.Services.EntityService)); + + //FROM Property TO ContentPropertyDto + config.CreateMap() + .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService, applicationContext.Services.EntityService)); + + //FROM Property TO ContentPropertyDisplay + config.CreateMap() + .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.EntityService)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index af6f99a15d..76e841d65a 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -67,8 +67,8 @@ namespace Umbraco.Web.Models.Mapping // If we have a prop editor, then format the pre-values based on it and create it's fields if (propEd != null) { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).AsEnumerable(); - if (source.IsBuildInDataType) + result = propEd.PreValueEditor.Fields.Select(Mapper.Map); + if (source.IsBuildInDataType()) { result = RemovePreValuesNotSupportedOnBuildInTypes(result); } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 8845e9c323..9cfa29985b 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -15,31 +15,6 @@ namespace Umbraco.Web.Search { internal class UmbracoTreeSearcher { - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable ExamineSearch( - UmbracoHelper umbracoHelper, - string query, - UmbracoEntityTypes entityType, - int pageSize, - long pageIndex, out long totalFound, string searchFrom = null) - { - return ExamineSearch(umbracoHelper, query, entityType, pageSize, pageIndex, out totalFound, false, searchFrom); - } - /// /// Searches for results based on the entity type /// @@ -59,7 +34,7 @@ namespace Umbraco.Web.Search string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, out long totalFound, bool ignoreUserStartNodes, string searchFrom = null) + long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) { var sb = new StringBuilder(); diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index f2f83d1133..995cb031d0 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -10,6 +10,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; using Umbraco.Web.Search; namespace Umbraco.Web.Trees diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index dfdea268c4..13bceb318f 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -73,24 +73,6 @@ namespace Umbraco.Web.WebApi.Filters throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } - var ignoreUserStartNodes = false; - - if (actionContext.ActionArguments.ContainsKey("dataTypeId")) - { - if (actionContext.ActionArguments.TryGetValue("dataTypeId", out var dataTypeIdValue)) - { - var dataTypeIdString = dataTypeIdValue?.ToString(); - if (string.IsNullOrEmpty(dataTypeIdString) == false && - Guid.TryParse(dataTypeIdString, out var dataTypeId)) - { - ignoreUserStartNodes = - ApplicationContext.Current.Services.DataTypeService - .IsDataTypeIgnoringUserStartNodes(dataTypeId); - } - } - - } - int nodeId; if (_nodeId.HasValue == false) { @@ -144,9 +126,7 @@ namespace Umbraco.Web.WebApi.Filters ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.ContentService, ApplicationContext.Current.Services.EntityService, - nodeId, - _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, - ignoreUserStartNodes: ignoreUserStartNodes)) + nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 22ce7a5ce7..33c99cd60a 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,12 +3,11 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Trees; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi.Filters { @@ -78,20 +77,9 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - - Guid? dataTypeId = Guid.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.DataTypeId), out var temp) ? (Guid?)temp : null; - - if (dataTypeId.HasValue == false) return; - - var ignoreUserStartNodes = ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - - if (ignoreUserStartNodes == false) - { - FilterBasedOnStartNode(items, user); - } + FilterBasedOnStartNode(items, user); } - internal void FilterBasedOnStartNode(IList items, IUser user) { var toRemove = new List(); From 9f45d41a4a6b9e88a89b31b60a7b695943022434 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 13:18:15 +0200 Subject: [PATCH 23/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Test fixes --- .../src/common/resources/entity.resource.js | 6 ++++-- .../grid/editors/rte.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 416fd0a286..dce2cd8347 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -187,8 +187,10 @@ function entityResource($q, $http, umbRequestHelper) { $http.post( umbRequestHelper.getApiUrl( "entityApiBaseUrl", - "GetAnchors", - { rteContent: rteContent })), + 'GetAnchors'), + { + rteContent: rteContent + }), 'Failed to anchors data for rte content ' + rteContent); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 601c0a36d2..da869dbf01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -12,7 +12,7 @@ function openLinkPicker(editor, currentTarget, anchorElement) { - entityResource.getAnchors($scope.model.value).then(function(anchorValues) { + entityResource.getAnchors(JSON.stringify($scope.model.value)).then(function(anchorValues) { vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 6c9cc7c36b..ebdfe59210 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -276,20 +276,23 @@ namespace Umbraco.Web.Editors [HttpGet] - public UrlAndAnchors GetUrlAndAnchors(int id) + public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) { - var x = GetResultForId(id, UmbracoEntityTypes.Document); - var url = Umbraco.Url(id); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); return new UrlAndAnchors(url, anchorValues); } - [HttpPost] - public IEnumerable GetAnchors(string rteContent) + public class AnchorsModel { + public string RteContent { get; set; } + } - var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); + [HttpGet] + [HttpPost] + public IEnumerable GetAnchors(AnchorsModel model) + { + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); return anchorValues; } From 2f309a2fd625ffa4ddc07f51f2516466dd0e3822 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 15:09:37 +0200 Subject: [PATCH 24/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - moved the code that adds the ufprt token to the context items, into the constructor of UmbracoForm. Because it needs to happen before the @Html.AntiForgeryToken() is called. The dispose method is too late. --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 2017 +++++++++-------- 1 file changed, 1009 insertions(+), 1008 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index a64c00a03c..0e2a2a6450 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -1,1008 +1,1009 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Helpers; -using System.Web.Mvc; -using System.Web.Mvc.Html; -using System.Web.Routing; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Profiling; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Constants = Umbraco.Core.Constants; -using Member = umbraco.cms.businesslogic.member.Member; - -namespace Umbraco.Web -{ - /// - /// HtmlHelper extensions for use in templates - /// - public static class HtmlHelperRenderExtensions - { - /// - /// Renders the markup for the profiler - /// - /// - /// - public static IHtmlString RenderProfiler(this HtmlHelper helper) - { - return new HtmlString(ProfilerResolver.Current.Profiler.Render()); - } - - /// - /// Renders a partial view that is found in the specified area - /// - /// - /// - /// - /// - /// - /// - public static MvcHtmlString AreaPartial(this HtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null) - { - var originalArea = helper.ViewContext.RouteData.DataTokens["area"]; - helper.ViewContext.RouteData.DataTokens["area"] = area; - var result = helper.Partial(partial, model, viewData); - helper.ViewContext.RouteData.DataTokens["area"] = originalArea; - return result; - } - - /// - /// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are - /// using does not inherit from UmbracoTemplatePage - /// - /// - /// - /// - /// See: http://issues.umbraco.org/issue/U4-1614 - /// - public static MvcHtmlString PreviewBadge(this HtmlHelper helper) - { - if (UmbracoContext.Current.InPreviewMode) - { - var htmlBadge = - String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), - UmbracoContext.Current.HttpContext.Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); - return new MvcHtmlString(htmlBadge); - } - return new MvcHtmlString(""); - - } - - public static IHtmlString CachedPartial( - this HtmlHelper htmlHelper, - string partialViewName, - object model, - int cachedSeconds, - bool cacheByPage = false, - bool cacheByMember = false, - ViewDataDictionary viewData = null, - Func contextualKeyBuilder = null) - { - var cacheKey = new StringBuilder(partialViewName); - if (cacheByPage) - { - if (UmbracoContext.Current == null) - { - throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); - } - cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); - } - if (cacheByMember) - { - var currentMember = Member.GetCurrentMember(); - cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); - } - if (contextualKeyBuilder != null) - { - var contextualKey = contextualKeyBuilder(model, viewData); - cacheKey.AppendFormat("c{0}-", contextualKey); - } - return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); - } - - public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) - where T : new() - { - var model = new T(); - var typedHelper = new HtmlHelper( - htmlHelper.ViewContext.CopyWithModel(model), - htmlHelper.ViewDataContainer.CopyWithModel(model)); - - return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); - } - - /// - /// A validation summary that lets you pass in a prefix so that the summary only displays for elements - /// containing the prefix. This allows you to have more than on validation summary on a page. - /// - /// - /// - /// - /// - /// - /// - public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, - string prefix = "", - bool excludePropertyErrors = false, - string message = "", - IDictionary htmlAttributes = null) - { - if (prefix.IsNullOrWhiteSpace()) - { - return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } - - //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for - //specific model state with the prefix. - var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); - return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } - - /// - /// Returns the result of a child action of a strongly typed SurfaceController - /// - /// - /// - /// - /// - public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) - where T : SurfaceController - { - return htmlHelper.Action(actionName, typeof(T)); - } - - /// - /// Returns the result of a child action of a SurfaceController - /// - /// - /// - /// - /// - /// - public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) - { - Mandate.ParameterNotNull(surfaceType, "surfaceType"); - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); - - var routeVals = new RouteValueDictionary(new {area = ""}); - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (!metaData.AreaName.IsNullOrWhiteSpace()) - { - //set the area to the plugin area - if (routeVals.ContainsKey("area")) - { - routeVals["area"] = metaData.AreaName; - } - else - { - routeVals.Add("area", metaData.AreaName); - } - } - - return htmlHelper.Action(actionName, metaData.ControllerName, routeVals); - } - - #region GetCropUrl - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string cropAlias) - { - return new HtmlString(mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias) - { - return new HtmlString(mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, - IPublishedContent mediaItem, - int? width = null, - int? height = null, - string propertyAlias = Constants.Conventions.Media.File, - string cropAlias = null, - int? quality = null, - ImageCropMode? imageCropMode = null, - ImageCropAnchor? imageCropAnchor = null, - bool preferFocalPoint = false, - bool useCropDimensions = false, - bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - return - new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, - string imageUrl, - int? width = null, - int? height = null, - string imageCropperValue = null, - string cropAlias = null, - int? quality = null, - ImageCropMode? imageCropMode = null, - ImageCropAnchor? imageCropAnchor = null, - bool preferFocalPoint = false, - bool useCropDimensions = false, - string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - return - new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, - upScale)); - } - - #endregion - - #region BeginUmbracoForm - - /// - /// Used for rendering out the Form for BeginUmbracoForm - /// - internal class UmbracoForm : MvcForm - { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - /// - public UmbracoForm( - ViewContext viewContext, - string controllerName, - string controllerAction, - string area, - FormMethod method, - object additionalRouteVals = null) - : base(viewContext) - { - _viewContext = viewContext; - _method = method; - _controllerName = controllerName; - _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); - } - - private readonly ViewContext _viewContext; - private readonly FormMethod _method; - private bool _disposed; - private readonly string _encryptedString; - private readonly string _controllerName; - - protected override void Dispose(bool disposing) - { - if (this._disposed) - return; - this._disposed = true; - - //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. - //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against - //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, - //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique - //per user. - _viewContext.HttpContext.Items["ufprt"] = _encryptedString; - - //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() - // We have a controllerName and area so we can match - if (_controllerName == "UmbRegister" - || _controllerName == "UmbProfile" - || _controllerName == "UmbLoginStatus" - || _controllerName == "UmbLogin") - { - _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); - } - - //write out the hidden surface form routes - _viewContext.Writer.Write(""); - - base.Dispose(disposing); - } - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) - { - return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) - { - return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); - - var area = ""; - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (metaData.AreaName.IsNullOrWhiteSpace() == false) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) - { - return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; - return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, - object additionalRouteVals, - IDictionary htmlAttributes) - { - return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); - } - - /// - /// This renders out the form for us - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This code is pretty much the same as the underlying MVC code that writes out the form - /// - private static MvcForm RenderForm(this HtmlHelper htmlHelper, - string formAction, - FormMethod method, - IDictionary htmlAttributes, - string surfaceController, - string surfaceAction, - string area, - object additionalRouteVals = null) - { - - //ensure that the multipart/form-data is added to the html attributes - if (htmlAttributes.ContainsKey("enctype") == false) - { - htmlAttributes.Add("enctype", "multipart/form-data"); - } - - var tagBuilder = new TagBuilder("form"); - tagBuilder.MergeAttributes(htmlAttributes); - // action is implicitly generated, so htmlAttributes take precedence. - tagBuilder.MergeAttribute("action", formAction); - // method is an explicit parameter, so it takes precedence over the htmlAttributes. - tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); - var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; - if (traditionalJavascriptEnabled) - { - // forms must have an ID for client validation - tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); - } - htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); - - //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); - - if (traditionalJavascriptEnabled) - { - htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; - } - return theForm; - } - - #endregion - - #region Wrap - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, (object)null); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - var item = html.Wrap(tag, innerText, anonymousAttributes); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - return html.Wrap(tag, innerText, (object)null); - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var wrap = new HtmlTagWrapper(tag); - if (anonymousAttributes != null) - { - wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); - } - if (!string.IsNullOrWhiteSpace(innerText)) - { - wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); - } - foreach (var child in children) - { - wrap.AddChild(child); - } - return wrap; - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, anonymousAttributes, children); - item.Visible = visible; - return item; - } - - #endregion - - #region canvasdesigner - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx) - { - return html.EnableCanvasDesigner(url, umbCtx, string.Empty, string.Empty); - } - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx, string canvasdesignerConfigPath) - { - return html.EnableCanvasDesigner(url, umbCtx, canvasdesignerConfigPath, string.Empty); - } - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) - { - - var umbracoPath = url.Content(SystemDirectories.Umbraco); - - string previewLink = @"" + - @"" + - @"" + - @"" + - @""; - - string noPreviewLinks = @""; - - // Get page value - int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; - string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; - string result = string.Empty; - string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); - - if (umbCtx.InPreviewMode) - { - canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false - ? canvasdesignerConfigPath - : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); - canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false - ? canvasdesignerPalettesPath - : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); - - if (string.IsNullOrEmpty(cssPath) == false) - result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; - - result = result + string.Format(previewLink, umbracoPath, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); - } - else - { - // Get css path for current page - if (string.IsNullOrEmpty(cssPath) == false) - result = string.Format(noPreviewLinks, cssPath); - } - - return new HtmlString(result); - - } - - #endregion - - #region RelatedLink - - /// - /// Renders an anchor element for a RelatedLink instance. - /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An anchor element - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) - { - return htmlHelper.GetRelatedLinkHtml(relatedLink, null); - } - - /// - /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. - /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An object that contains the HTML attributes to set for the element. - /// - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) - { - var tagBuilder = new TagBuilder("a"); - tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - tagBuilder.MergeAttribute("href", relatedLink.Link); - tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); - tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); - return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); - } - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Helpers; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Dynamics; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Profiling; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Constants = Umbraco.Core.Constants; +using Member = umbraco.cms.businesslogic.member.Member; + +namespace Umbraco.Web +{ + /// + /// HtmlHelper extensions for use in templates + /// + public static class HtmlHelperRenderExtensions + { + /// + /// Renders the markup for the profiler + /// + /// + /// + public static IHtmlString RenderProfiler(this HtmlHelper helper) + { + return new HtmlString(ProfilerResolver.Current.Profiler.Render()); + } + + /// + /// Renders a partial view that is found in the specified area + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString AreaPartial(this HtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null) + { + var originalArea = helper.ViewContext.RouteData.DataTokens["area"]; + helper.ViewContext.RouteData.DataTokens["area"] = area; + var result = helper.Partial(partial, model, viewData); + helper.ViewContext.RouteData.DataTokens["area"] = originalArea; + return result; + } + + /// + /// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are + /// using does not inherit from UmbracoTemplatePage + /// + /// + /// + /// + /// See: http://issues.umbraco.org/issue/U4-1614 + /// + public static MvcHtmlString PreviewBadge(this HtmlHelper helper) + { + if (UmbracoContext.Current.InPreviewMode) + { + var htmlBadge = + String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, + IOHelper.ResolveUrl(SystemDirectories.Umbraco), + IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), + UmbracoContext.Current.HttpContext.Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); + return new MvcHtmlString(htmlBadge); + } + return new MvcHtmlString(""); + + } + + public static IHtmlString CachedPartial( + this HtmlHelper htmlHelper, + string partialViewName, + object model, + int cachedSeconds, + bool cacheByPage = false, + bool cacheByMember = false, + ViewDataDictionary viewData = null, + Func contextualKeyBuilder = null) + { + var cacheKey = new StringBuilder(partialViewName); + if (cacheByPage) + { + if (UmbracoContext.Current == null) + { + throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); + } + cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); + } + if (cacheByMember) + { + var currentMember = Member.GetCurrentMember(); + cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); + } + if (contextualKeyBuilder != null) + { + var contextualKey = contextualKeyBuilder(model, viewData); + cacheKey.AppendFormat("c{0}-", contextualKey); + } + return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); + } + + public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) + where T : new() + { + var model = new T(); + var typedHelper = new HtmlHelper( + htmlHelper.ViewContext.CopyWithModel(model), + htmlHelper.ViewDataContainer.CopyWithModel(model)); + + return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); + } + + /// + /// A validation summary that lets you pass in a prefix so that the summary only displays for elements + /// containing the prefix. This allows you to have more than on validation summary on a page. + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, + string prefix = "", + bool excludePropertyErrors = false, + string message = "", + IDictionary htmlAttributes = null) + { + if (prefix.IsNullOrWhiteSpace()) + { + return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for + //specific model state with the prefix. + var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); + return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + /// + /// Returns the result of a child action of a strongly typed SurfaceController + /// + /// + /// + /// + /// + public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) + where T : SurfaceController + { + return htmlHelper.Action(actionName, typeof(T)); + } + + /// + /// Returns the result of a child action of a SurfaceController + /// + /// + /// + /// + /// + /// + public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) + { + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); + + var routeVals = new RouteValueDictionary(new {area = ""}); + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (!metaData.AreaName.IsNullOrWhiteSpace()) + { + //set the area to the plugin area + if (routeVals.ContainsKey("area")) + { + routeVals["area"] = metaData.AreaName; + } + else + { + routeVals.Add("area", metaData.AreaName); + } + } + + return htmlHelper.Action(actionName, metaData.ControllerName, routeVals); + } + + #region GetCropUrl + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + IPublishedContent mediaItem, + int? width = null, + int? height = null, + string propertyAlias = Constants.Conventions.Media.File, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + bool cacheBuster = true, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, + upScale)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + string imageUrl, + int? width = null, + int? height = null, + string imageCropperValue = null, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + string cacheBusterValue = null, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, + upScale)); + } + + #endregion + + #region BeginUmbracoForm + + /// + /// Used for rendering out the Form for BeginUmbracoForm + /// + internal class UmbracoForm : MvcForm + { + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( + ViewContext viewContext, + string controllerName, + string controllerAction, + string area, + FormMethod method, + object additionalRouteVals = null) + : base(viewContext) + { + _viewContext = viewContext; + _method = method; + _controllerName = controllerName; + _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); + + //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. + //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against + //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, + //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique + //per user. + _viewContext.HttpContext.Items["ufprt"] = _encryptedString; + + } + + + private readonly ViewContext _viewContext; + private readonly FormMethod _method; + private bool _disposed; + private readonly string _encryptedString; + private readonly string _controllerName; + + protected override void Dispose(bool disposing) + { + if (this._disposed) + return; + this._disposed = true; + //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() + // We have a controllerName and area so we can match + if (_controllerName == "UmbRegister" + || _controllerName == "UmbProfile" + || _controllerName == "UmbLoginStatus" + || _controllerName == "UmbLogin") + { + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + } + + //write out the hidden surface form routes + _viewContext.Writer.Write(""); + + base.Dispose(disposing); + } + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; + return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes) + { + return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); + } + + /// + /// This renders out the form for us + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// This code is pretty much the same as the underlying MVC code that writes out the form + /// + private static MvcForm RenderForm(this HtmlHelper htmlHelper, + string formAction, + FormMethod method, + IDictionary htmlAttributes, + string surfaceController, + string surfaceAction, + string area, + object additionalRouteVals = null) + { + + //ensure that the multipart/form-data is added to the html attributes + if (htmlAttributes.ContainsKey("enctype") == false) + { + htmlAttributes.Add("enctype", "multipart/form-data"); + } + + var tagBuilder = new TagBuilder("form"); + tagBuilder.MergeAttributes(htmlAttributes); + // action is implicitly generated, so htmlAttributes take precedence. + tagBuilder.MergeAttribute("action", formAction); + // method is an explicit parameter, so it takes precedence over the htmlAttributes. + tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; + if (traditionalJavascriptEnabled) + { + // forms must have an ID for client validation + tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); + } + htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); + + //new UmbracoForm: + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); + + if (traditionalJavascriptEnabled) + { + htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; + } + return theForm; + } + + #endregion + + #region Wrap + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, (object)null); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + var item = html.Wrap(tag, innerText, anonymousAttributes); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + return html.Wrap(tag, innerText, (object)null); + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var wrap = new HtmlTagWrapper(tag); + if (anonymousAttributes != null) + { + wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + } + if (!string.IsNullOrWhiteSpace(innerText)) + { + wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); + } + foreach (var child in children) + { + wrap.AddChild(child); + } + return wrap; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, anonymousAttributes, children); + item.Visible = visible; + return item; + } + + #endregion + + #region canvasdesigner + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx) + { + return html.EnableCanvasDesigner(url, umbCtx, string.Empty, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath) + { + return html.EnableCanvasDesigner(url, umbCtx, canvasdesignerConfigPath, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) + { + + var umbracoPath = url.Content(SystemDirectories.Umbraco); + + string previewLink = @"" + + @"" + + @"" + + @"" + + @""; + + string noPreviewLinks = @""; + + // Get page value + int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; + string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; + string result = string.Empty; + string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); + + if (umbCtx.InPreviewMode) + { + canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false + ? canvasdesignerConfigPath + : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); + canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false + ? canvasdesignerPalettesPath + : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); + + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; + + result = result + string.Format(previewLink, umbracoPath, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); + } + else + { + // Get css path for current page + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath); + } + + return new HtmlString(result); + + } + + #endregion + + #region RelatedLink + + /// + /// Renders an anchor element for a RelatedLink instance. + /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An anchor element + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) + { + return htmlHelper.GetRelatedLinkHtml(relatedLink, null); + } + + /// + /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. + /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An object that contains the HTML attributes to set for the element. + /// + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) + { + var tagBuilder = new TagBuilder("a"); + tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + tagBuilder.MergeAttribute("href", relatedLink.Link); + tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); + tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); + return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); + } + #endregion + } +} From cf53ba363d1f32e6daedce98da8b10cb69f789d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:29:05 +1000 Subject: [PATCH 25/35] Internalizes new methods classes + cleanup --- src/Umbraco.Core/Constants-DataTypes.cs | 2 +- ...nitionExtensions.cs => DataTypeExtensions.cs} | 16 ++++++++++++---- .../Services/DateTypeServiceExtensions.cs | 7 +++++-- 3 files changed, 18 insertions(+), 7 deletions(-) rename src/Umbraco.Core/Models/{DataTypeDefinitionExtensions.cs => DataTypeExtensions.cs} (79%) diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index d5d0e99dab..0e95e57b1b 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core /// /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. /// - public static class DataTypes + internal static class DataTypes { public static class ReservedPreValueKeys diff --git a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs similarity index 79% rename from src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs rename to src/Umbraco.Core/Models/DataTypeExtensions.cs index 45c4420820..47702de2c5 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Models { - public static class DataTypeDefinitionExtensions + internal static class DataTypeExtensions { private static readonly ISet IdsOfBuildInDataTypes = new HashSet() { @@ -37,11 +37,19 @@ namespace Umbraco.Core.Models /// /// Returns true if this date type is build-in/default. /// - /// The data type definition. + /// The data type definition. /// - public static bool IsBuildInDataType(this IDataTypeDefinition dataTypeDefinition) + public static bool IsBuildInDataType(this IDataTypeDefinition dataType) { - return IdsOfBuildInDataTypes.Contains(dataTypeDefinition.Key); + return IsBuildInDataType(dataType.Key); + } + + /// + /// Returns true if this date type is build-in/default. + /// + public static bool IsBuildInDataType(Guid key) + { + return IdsOfBuildInDataTypes.Contains(key); } } } diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs index 63006653ab..bea83558a5 100644 --- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -1,17 +1,20 @@ using System; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { - public static class DateTypeServiceExtensions + internal static class DateTypeServiceExtensions { public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) { + if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes + var dataType = dataTypeService.GetDataTypeDefinitionById(key); if (dataType != null) { var preValues = dataTypeService.GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue( + if (preValues.FormatAsDictionary().TryGetValue( Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) return preValue.Value.InvariantEquals("1"); } From 23b8e1cce8107aabd8ba5ebbcb0a3ee33aff212d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:31:04 +1000 Subject: [PATCH 26/35] Removes the remaining part of the EntityRepository that was loading in ALL properties for media which we don't want whatsoever which means some other code is cleaned up/removed --- src/Umbraco.Core/Models/UmbracoEntity.cs | 39 +---- .../Repositories/EntityRepository.cs | 146 ++---------------- src/Umbraco.Core/Services/EntityService.cs | 21 --- .../Models/UmbracoEntityTests.cs | 27 +--- .../Trees/ContentTreeController.cs | 3 - .../Trees/ContentTreeControllerBase.cs | 8 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../WebServices/FolderBrowserService.cs | 114 -------------- .../umbraco/Trees/BaseMediaTree.cs | 31 +--- .../umbraco/Trees/BaseTree.cs | 2 +- 11 files changed, 27 insertions(+), 373 deletions(-) delete mode 100644 src/Umbraco.Web/WebServices/FolderBrowserService.cs diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index b789698704..7783331952 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -225,42 +225,5 @@ namespace Umbraco.Core.Models return clone; } - /// - /// A struction that can be contained in the additional data of an UmbracoEntity representing - /// a user defined property - /// - public class EntityProperty : IDeepCloneable - { - public string PropertyEditorAlias { get; set; } - public object Value { get; set; } - public object DeepClone() - { - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = MemberwiseClone(); - return clone; - } - - protected bool Equals(EntityProperty other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EntityProperty) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 495c0109ad..7b10b19e1e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -20,27 +20,22 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class EntityRepository : DisposableObjectSlim, IEntityRepository { - private readonly IDatabaseUnitOfWork _work; - public EntityRepository(IDatabaseUnitOfWork work) { - _work = work; + UnitOfWork = work; } /// /// Returns the Unit of Work added to the repository /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } + protected internal IDatabaseUnitOfWork UnitOfWork { get; } /// /// Internal for testing purposes /// internal Guid UnitKey { - get { return (Guid)_work.Key; } + get { return (Guid)UnitOfWork.Key; } } #region Query Methods @@ -69,75 +64,8 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable result; - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new { ids = idGroup }); - propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } + var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. @@ -159,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories var translatorCount = new SqlTranslator(sqlCountClause, query); var countSql = translatorCount.Translate(); - totalRecords = _work.Database.ExecuteScalar(countSql); + totalRecords = UnitOfWork.Database.ExecuteScalar(countSql); return result; } @@ -167,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories public IUmbracoEntity GetByKey(Guid key) { var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -187,7 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -202,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id) { var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -222,7 +150,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -256,7 +184,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -283,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); if (filter != null) filter(sql); - return _work.Database.Fetch(sql); + return UnitOfWork.Database.Fetch(sql); } public virtual IEnumerable GetByQuery(IQuery query) @@ -292,7 +220,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); - var dtos = _work.Database.Fetch(sql); + var dtos = UnitOfWork.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); @@ -306,10 +234,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// - /// Note that this will also fetch all property data for media items, which can cause performance problems - /// when used without paging, in sites with large amounts of data in cmsPropertyData. - /// public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; @@ -323,27 +247,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetByQueryInternal(entitySql, isContent, isMedia); } - /// - /// Gets entities by query without fetching property data. - /// - /// - /// - /// - /// - /// This is supposed to be internal and can be used when getting all entities without paging, without causing - /// performance issues. - /// - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - return GetByQueryInternal(entitySql, false, true); - } - - internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) + private IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) { var factory = new UmbracoEntityFactory(); @@ -351,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories var finalSql = entitySql.Append(GetGroupBy(isContent, isMedia)); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); + var dtos = UnitOfWork.Database.Query(finalSql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -392,22 +296,6 @@ namespace Umbraco.Core.Persistence.Repositories return entitySql.Append(GetGroupBy(isContent, true, false)); } - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .From() - .InnerJoin() - .On(dto => dto.NodeId, dto => dto.NodeId) - .InnerJoin() - .On(dto => dto.Id, dto => dto.PropertyTypeId) - .InnerJoin() - .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType", new { nodeObjectType = nodeObjectType }); - - return sql; - } - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { return GetBase(isContent, isMedia, customFilter, false); @@ -638,13 +526,13 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(Guid key) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } public bool Exists(int id) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } #region private classes diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 906ece1081..f4b1b71732 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -234,27 +234,6 @@ namespace Umbraco.Core.Services } } - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); - } - } - /// /// Returns a paged collection of children /// diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 7186474999..1ab6bbea31 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -177,18 +177,7 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var clone = (UmbracoEntity)item.DeepClone(); Assert.AreNotSame(clone, item); @@ -250,20 +239,10 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); Debug.Print(json); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 54adb24b74..e7fb0b8d84 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -207,9 +207,6 @@ namespace Umbraco.Web.Trees return HasPathAccess(entity, queryStrings); } - internal override IEnumerable GetChildrenFromEntityService(int entityId) - => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); - /// /// Returns a collection of all menu items that can be on a content node /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2624c89b56..4e4086a3c6 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -205,12 +205,8 @@ namespace Umbraco.Web.Trees return GetChildrenFromEntityService(entityId); } - /// - /// Abstract method to fetch the entities from the entity service - /// - /// - /// - internal abstract IEnumerable GetChildrenFromEntityService(int entityId); + private IEnumerable GetChildrenFromEntityService(int entityId) + => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); /// /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index f2f59b6bd7..50336dbc10 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -177,12 +177,6 @@ namespace Umbraco.Web.Trees { return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } - - internal override IEnumerable GetChildrenFromEntityService(int entityId) - // Not pretty having to cast the service, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - => ((EntityService)Services.EntityService).GetMediaChildrenWithoutPropertyData(entityId).ToList(); + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d4fd27c323..32eaae1077 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1812,7 +1812,6 @@ - diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs deleted file mode 100644 index 15a6c10880..0000000000 --- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Script.Serialization; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web.Media.ThumbnailProviders; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.Tags; -using Umbraco.Web.BaseRest; -using Tag = umbraco.cms.businesslogic.Tags.Tag; - -namespace Umbraco.Web.WebServices -{ - //TODO: Can we convert this to MVC please instead of /base? - [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - [RestExtension("FolderBrowserService")] - public class FolderBrowserService - { - [RestExtensionMethod(ReturnXml = false)] - public static string GetChildren(int parentId) - { - var currentUser = GetCurrentUser(); - AuthorizeAccess(parentId, currentUser); - - // Get children and filter - var data = new List(); - var service = ApplicationContext.Current.Services.EntityService; - - var entities = service.GetChildren(parentId, UmbracoObjectTypes.Media); - foreach (UmbracoEntity entity in entities) - { - var uploadFieldProperty = entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null) - .FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - //var uploadFieldProperty = entity.UmbracoProperties.FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - var thumbnailUrl = uploadFieldProperty == null ? "" : ThumbnailProvidersResolver.Current.GetThumbnailUrl((string)uploadFieldProperty.Value); - - var item = new - { - Id = entity.Id, - Path = entity.Path, - Name = entity.Name, - Tags = string.Join(",", Tag.GetTags(entity.Id).Select(x => x.TagCaption)), - MediaTypeAlias = entity.ContentTypeAlias, - EditUrl = string.Format("editMedia.aspx?id={0}", entity.Id), - FileUrl = uploadFieldProperty == null - ? "" - : uploadFieldProperty.Value, - ThumbnailUrl = string.IsNullOrEmpty(thumbnailUrl) - ? IOHelper.ResolveUrl(string.Format("{0}/images/thumbnails/{1}", SystemDirectories.Umbraco, entity.ContentTypeThumbnail)) - : thumbnailUrl - }; - - data.Add(item); - - } - - return new JavaScriptSerializer().Serialize(data); - } - - [RestExtensionMethod(ReturnXml = false)] - public static string Delete(string nodeIds) - { - var currentUser = GetCurrentUser(); - - var nodeIdParts = nodeIds.Split(','); - - foreach (var nodeIdPart in nodeIdParts.Where(x => string.IsNullOrEmpty(x) == false)) - { - int nodeId; - if (Int32.TryParse(nodeIdPart, out nodeId) == false) - continue; - - var node = new global::umbraco.cms.businesslogic.media.Media(nodeId); - AuthorizeAccess(node, currentUser); - - node.delete(("," + node.Path + ",").Contains(",-21,")); - } - - return new JavaScriptSerializer().Serialize(new { success = true }); - } - - private static User GetCurrentUser() - { - var currentUser = User.GetCurrent(); - if (currentUser == null) - throw new UnauthorizedAccessException("You must be logged in to use this service"); - - return currentUser; - } - - private static void AuthorizeAccess(global::umbraco.cms.businesslogic.media.Media mediaItem, User currentUser) - { - if (("," + mediaItem.Path + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - - private static void AuthorizeAccess(int parentId, User currentUser) - { - var service = ApplicationContext.Current.Services.EntityService; - var parentMedia = service.Get(parentId, UmbracoObjectTypes.Media); - var mediaPath = parentMedia == null ? parentId.ToString(CultureInfo.InvariantCulture) : parentMedia.Path; - - if (("," + mediaPath + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs index f72457d867..f7412f6915 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs @@ -189,35 +189,8 @@ function openMedia(id) { return ""; } - /// - /// NOTE: New implementation of the legacy GetLinkValue. This is however a bit quirky as a media item can have multiple "Linkable DataTypes". - /// Returns the value for a link in WYSIWYG mode, by default only media items that have a - /// DataTypeUploadField are linkable, however, a custom tree can be created which overrides - /// this method, or another GUID for a custom data type can be added to the LinkableMediaDataTypes - /// list on application startup. - /// - /// - /// - internal virtual string GetLinkValue(UmbracoEntity entity) - { - foreach (var property in entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null)) - { - - - //required for backwards compatibility with v7 with changing the GUID -> alias - var controlId = LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias(property.PropertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.ReturnNull); - if (controlId != null) - { - if (LinkableMediaDataTypes.Contains(controlId.Value) - && string.IsNullOrEmpty((string)property.Value) == false) - - return property.Value.ToString(); - } - } - return ""; - } + [Obsolete("Just like this class is and is not used whatsoever for a very long time, this method will return an empty string")] + internal virtual string GetLinkValue(UmbracoEntity entity) => string.Empty; /// /// By default, any media type that is to be "linkable" in the WYSIWYG editor must contain diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs index 49d8c7214d..ab18a1316e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs @@ -652,4 +652,4 @@ namespace umbraco.cms.presentation.Trees } -} \ No newline at end of file +} From ca15f340b09b8a9df53a49a5089f3ceb7edc6a15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:32:16 +1000 Subject: [PATCH 27/35] Fixes a bunch of N+1, content/media trees should be much more snappy --- src/Umbraco.Core/Models/UserExtensions.cs | 13 ----- .../Factories/UmbracoEntityFactory.cs | 16 +++--- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Trees/ContentTreeControllerBase.cs | 54 ++++++++++++++++--- src/Umbraco.Web/Trees/TreeControllerBase.cs | 16 ------ 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index d989876607..6c29ab56bb 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -193,19 +193,6 @@ namespace Umbraco.Core.Models // check for a start node in the path return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ","))); - } - - internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) - { - switch (recycleBinId) - { - case Constants.System.RecycleBinMedia: - return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); - case Constants.System.RecycleBinContent: - return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); - default: - throw new NotSupportedException("Path access is only determined on content or media"); - } } internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 86594836db..f12af4d27a 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -12,14 +12,18 @@ namespace Umbraco.Core.Persistence.Factories { internal class UmbracoEntityFactory { + private static readonly Lazy EntityProperties = new Lazy(() => typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray()); + + /// + /// Figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + /// + /// + /// internal void AddAdditionalData(UmbracoEntity entity, IDictionary originalEntityProperties) - { - var entityProps = typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray(); - - //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + { foreach (var k in originalEntityProperties.Keys .Select(x => new { orig = x, title = x.ToCleanString(CleanStringType.PascalCase | CleanStringType.Ascii | CleanStringType.ConvertCase) }) - .Where(x => entityProps.InvariantContains(x.title) == false)) + .Where(x => EntityProperties.Value.InvariantContains(x.title) == false)) { entity.AdditionalData[k.title] = originalEntityProperties[k.orig]; } @@ -78,4 +82,4 @@ namespace Umbraco.Core.Persistence.Factories } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 49f5e8b5d2..3b5d0aaff6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -377,7 +377,7 @@ - + diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 4e4086a3c6..d5c7c04235 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -87,11 +87,12 @@ namespace Umbraco.Web.Trees /// /// /// - internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings, + int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes) { bool hasPathAccess; - var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); - if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) + var entityIsAncestorOfStartNodes = UserExtensions.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out hasPathAccess); + if (ignoreUserStartNodes == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); @@ -101,13 +102,30 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) + if (ignoreUserStartNodes == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } return treeNode; } + private void GetUserStartNodes(out int[] startNodeIds, out string[] startNodePaths) + { + switch (RecycleBinId) + { + case Constants.System.RecycleBinMedia: + startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService); + break; + case Constants.System.RecycleBinContent: + startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService); + break; + default: + throw new NotSupportedException("Path access is only determined on content or media"); + } + } + /// /// Returns the /// @@ -134,6 +152,8 @@ namespace Umbraco.Web.Trees ? queryStrings.GetValue(TreeQueryStringParameters.StartNodeId) : string.Empty; + var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); + if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString) { // request has been made to render from a specific, non-root, start node @@ -141,7 +161,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) + if (ignoreUserStartNodes == false && HasPathAccess(id, queryStrings) == false) { LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; @@ -159,7 +179,11 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children var entities = GetChildEntities(id, queryStrings).ToList(); - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + + //get the current user start node/paths + GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); + + nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, // but to provide some context we also need to add their topmost nodes when they are not @@ -170,7 +194,7 @@ namespace Umbraco.Web.Trees if (topNodeIds.Length > 0) { var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); - nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); } } @@ -482,5 +506,21 @@ namespace Umbraco.Web.Trees } private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + internal bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 995cb031d0..8962a9ca10 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -350,22 +350,6 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); } - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); - if (dataTypeId.HasValue) - { - return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - } - - return false; - } - /// /// An event that allows developers to modify the tree node collection that is being rendered /// From 1b83e0a87da3a5c5287d4253700fd2de9ac8e2bf Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:49:45 +1000 Subject: [PATCH 28/35] cleanup --- .../src/common/resources/entity.resource.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index dce2cd8347..455fcab956 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -327,24 +327,13 @@ function entityResource($q, $http, umbRequestHelper) { * */ getAncestors: function (id, type, options) { - var defaults = { - dataTypeId: null - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - - + var args = [ { id: id }, { type: type } ]; - if(options.dataTypeId){ - args.push({dataTypeId: options.dataTypeId}); + if (options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); } return umbRequestHelper.resourcePromise( From a55323176f02e2c93150152da30302582f07f933 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 14:35:36 +1000 Subject: [PATCH 29/35] Updates EntityController to support passing in the dataTypeId for the GetChildren methods and updates the mediapicker to pass along that data --- .../src/common/resources/entity.resource.js | 12 +- .../mediaPicker/mediapicker.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 126 +++++++++++++----- 3 files changed, 103 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 455fcab956..5e6f1095e4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -358,14 +358,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getChildren: function (id, type) { + getChildren: function (id, type, options) { + + var args = [{ id: id }, { type: type }]; + if (options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetChildren", - [{ id: id }, { type: type }])), + args)), 'Failed to retrieve child data for id ' + id); }, @@ -433,7 +438,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId } )), 'Failed to retrieve child data for id ' + parentId); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 400d3a7bb5..96354d7696 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -388,7 +388,7 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return entityResource.getChildren(id, "Media") + return entityResource.getChildren(id, "Media", $scope.searchOptions) .then(function(data) { for (i=0;i /// - /// Some objects such as macros are not based on CMSNode + /// + /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access + /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing + /// Members, more explicit security checks are done. + /// + /// Some objects such as macros are not based on CMSNode /// [EntityControllerConfiguration] [PluginController("UmbracoApi")] @@ -88,8 +93,7 @@ namespace Umbraco.Web.Editors [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) { - //TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); @@ -195,6 +199,9 @@ namespace Umbraco.Web.Editors /// Int id of the entity to fetch URL for /// The tpye of entity such as Document, Media, Member /// The URL or path to the item + /// + /// We are not restricting this with security because there is no sensitive data + /// public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) { var returnUrl = string.Empty; @@ -428,9 +435,52 @@ namespace Umbraco.Web.Editors return GetResultForKeys(ids, type); } - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) { - return GetResultForChildren(id, type); + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchy here, some might not. + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return Enumerable.Empty(); + var pr = new List(nodes.Select(Mapper.Map)); + return pr; + } + + // else proceed as usual + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } } /// @@ -451,7 +501,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { int intId; @@ -480,7 +531,7 @@ namespace Umbraco.Web.Editors //the EntityService can search paged members from the root intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId); } //the EntityService cannot search members of a certain type, this is currently not supported and would require @@ -513,7 +564,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -523,8 +575,40 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { + IEnumerable entities; long totalRecords; - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + if (pageNumber > 0) + return new PagedResult(0, 0, 0); + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map) + }; + return pr; + } + + // else proceed as usual + entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); if (totalRecords == 0) { @@ -653,30 +737,6 @@ namespace Umbraco.Web.Editors return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom, ignoreUserStartNodes); } - - private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - return Services.EntityService.GetChildren(id, objectType.Value) - .WhereNotNull() - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, Guid? dataTypeId = null) { var objectType = ConvertToObjectType(entityType); From e7a30d0a5dacad7e821714bc2376cdf7f438144b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 26 Jun 2019 07:39:34 +0200 Subject: [PATCH 30/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - Fixes for gulp build --- .../src/common/resources/entity.resource.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5e6f1095e4..c85a85bb57 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -327,12 +327,12 @@ function entityResource($q, $http, umbRequestHelper) { * */ getAncestors: function (id, type, options) { - + var args = [ { id: id }, { type: type } ]; - if (options.dataTypeId) { + if (options && options.dataTypeId) { args.push({ dataTypeId: options.dataTypeId }); } @@ -361,7 +361,7 @@ function entityResource($q, $http, umbRequestHelper) { getChildren: function (id, type, options) { var args = [{ id: id }, { type: type }]; - if (options.dataTypeId) { + if (options && options.dataTypeId) { args.push({ dataTypeId: options.dataTypeId }); } From 571a3bc257d3289d0853cd400b799b0d2c21fa40 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 16:48:39 +1000 Subject: [PATCH 31/35] fixes test --- src/Umbraco.Tests/Plugins/PluginManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index ef2c61f6b2..94f56ba6e7 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -327,7 +327,7 @@ AnotherContentFinder public void Resolves_RestExtensions() { var types = _manager.ResolveRestExtensions(); - Assert.AreEqual(3, types.Count()); + Assert.AreEqual(2, types.Count()); } [Test] From 8033931e3163329a3d841d72364079aa62153a9a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 26 Jun 2019 09:58:01 +0200 Subject: [PATCH 32/35] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - Fixed merge conflict --- src/Umbraco.Web/Umbraco.Web.csproj | 6076 +++++++++------------------- 1 file changed, 2025 insertions(+), 4051 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6174fbb511..0b601f8f94 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,4054 +1,2028 @@ - - - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - -  - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file From fad33a97b24f835c8a09025bcbdff1ac7d52ce96 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Jun 2019 15:47:25 +1000 Subject: [PATCH 33/35] don't throw JS exception when no MediaPath, instead warn. --- .../src/common/services/mediahelper.service.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 2f31d36b62..38a3de8202 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper) { +function mediaHelper(umbRequestHelper, $log) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -144,7 +144,9 @@ function mediaHelper(umbRequestHelper) { resolveFileFromEntity: function (mediaEntity, thumbnail) { if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { - throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; + //don't throw since this image legitimately might not contain a media path, but output a warning + $log.warn("Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"); + return null; } if (thumbnail) { From a018ed9e526fbde52ebed7f6afafb8f92150d5ba Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 27 Jun 2019 13:27:34 +0100 Subject: [PATCH 34/35] V7: Less-Urgent-But-Still-Quite-Important - Add Giphy EmbeddedMedia support (#5736) --- src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config | 7 +++++++ src/Umbraco.Web.UI/config/EmbeddedMedia.config | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config index 603683cf30..f8c2be088d 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config @@ -130,5 +130,12 @@ + + + + + + + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.config index ccefad2983..d88e1d2b4d 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.config @@ -130,5 +130,12 @@ + + + + + + + From fa4acb255b6ebf81b7e285fa7486384d7e26e5c1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 17:09:11 +1000 Subject: [PATCH 35/35] fixes missing 'var' --- .../mediaPicker/mediapicker.controller.js | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 96354d7696..bb34ba379c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { if (!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); @@ -40,7 +40,7 @@ angular.module("umbraco") $scope.acceptedMediatypes = []; mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); @@ -107,24 +107,24 @@ angular.module("umbraco") } } - $scope.upload = function(v) { + $scope.upload = function (v) { angular.element(".umb-file-dropzone-directive .file-select").click(); }; - $scope.dragLeave = function(el, event) { + $scope.dragLeave = function (el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event) { + $scope.dragEnter = function (el, event) { $scope.activeDrag = true; }; - $scope.submitFolder = function() { + $scope.submitFolder = function () { if ($scope.newFolderName) { $scope.creatingFolder = true; mediaResource .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { + .then(function (data) { //we've added a new folder so lets clear the tree cache for that specific item treeService.clearCache({ cacheKey: "__media", //this is the main media tree cache key @@ -140,14 +140,14 @@ angular.module("umbraco") } }; - $scope.enterSubmitFolder = function(event) { + $scope.enterSubmitFolder = function (event) { if (event.keyCode === 13) { $scope.submitFolder(); event.stopPropagation(); } }; - $scope.gotoFolder = function(folder) { + $scope.gotoFolder = function (folder) { if (!$scope.multiPicker) { deselectAllImages($scope.model.selectedImages); } @@ -158,9 +158,9 @@ angular.module("umbraco") if (folder.id > 0) { entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) - .then(function(anc) { + .then(function (anc) { $scope.path = _.filter(anc, - function(f) { + function (f) { return f.path.indexOf($scope.startNodeId) !== -1; }); }); @@ -181,7 +181,7 @@ angular.module("umbraco") return getChildren(folder.id); }; - $scope.clickHandler = function(image, event, index) { + $scope.clickHandler = function (image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { $scope.gotoFolder(image); @@ -209,7 +209,7 @@ angular.module("umbraco") } }; - $scope.clickItemName = function(item) { + $scope.clickItemName = function (item) { if (item.isFolder) { $scope.gotoFolder(item); } @@ -241,8 +241,8 @@ angular.module("umbraco") images.length = 0; } - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { + $scope.onUploadComplete = function (files) { + $scope.gotoFolder($scope.currentFolder).then(function () { if (files.length === 1 && $scope.model.selectedImages.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; @@ -252,7 +252,7 @@ angular.module("umbraco") }); }; - $scope.onFilesQueue = function() { + $scope.onFilesQueue = function () { $scope.activeDrag = false; }; @@ -285,12 +285,12 @@ angular.module("umbraco") $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } - $scope.openDetailsDialog = function() { + $scope.openDetailsDialog = function () { $scope.mediaPickerDetailsOverlay = {}; $scope.mediaPickerDetailsOverlay.show = true; - $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.mediaPickerDetailsOverlay.submit = function (model) { $scope.model.selectedImages.push($scope.target); $scope.model.submit($scope.model); @@ -298,42 +298,42 @@ angular.module("umbraco") $scope.mediaPickerDetailsOverlay = null; }; - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.close = function (oldModel) { $scope.mediaPickerDetailsOverlay.show = false; $scope.mediaPickerDetailsOverlay = null; }; }; - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - dataTypeId: $scope.model.dataTypeId - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); + var debounceSearchMedia = _.debounce(function () { + $scope.$apply(function () { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); - $scope.changeSearch = function() { + $scope.changeSearch = function () { $scope.loading = true; debounceSearchMedia(); }; - $scope.toggle = function() { + $scope.toggle = function () { // Make sure to activate the changeSearch function everytime the toggle is clicked $scope.changeSearch(); } - $scope.changePagination = function(pageNumber) { + $scope.changePagination = function (pageNumber) { $scope.loading = true; $scope.searchOptions.pageNumber = pageNumber; searchMedia(); @@ -342,10 +342,10 @@ angular.module("umbraco") function searchMedia() { $scope.loading = true; entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { // update image data to work with image grid angular.forEach(data.items, - function(mediaItem) { + function (mediaItem) { // set thumbnail and src mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); @@ -389,10 +389,10 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; return entityResource.getChildren(id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { - for (i=0;i