diff --git a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.mocks.js b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.mocks.js index 1c5e60be6a..1a108f7ae3 100644 --- a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.mocks.js +++ b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.mocks.js @@ -1,4 +1,4 @@ -/*! umbraco - v0.0.1-SNAPSHOT - 2013-06-10 +/*! umbraco - v0.0.1-SNAPSHOT - 2013-06-11 * http://umbraco.github.io/Belle * Copyright (c) 2013 Per Ploug, Anders Stenteberg & Shannon Deminick; * Licensed MIT diff --git a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js index 5e13492793..fee6daa5e8 100644 --- a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js +++ b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.resources.js @@ -1,4 +1,4 @@ -/*! umbraco - v0.0.1-SNAPSHOT - 2013-06-10 +/*! umbraco - v0.0.1-SNAPSHOT - 2013-06-11 * http://umbraco.github.io/Belle * Copyright (c) 2013 Per Ploug, Anders Stenteberg & Shannon Deminick; * Licensed MIT @@ -26,7 +26,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return Umbraco.Sys.ServerVariables.contentApiBaseUrl + "PostSave"; } /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action) { + function saveContentItem(content, action, files) { var deferred = $q.defer(); //save the active tab id so we can set it when the data is returned. @@ -39,8 +39,14 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.postMultiPartRequest( getSaveUrl(content.id), { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data) { - //TODO: transform the request callback and add the files associated with the request + function (data, formData) { + //now add all of the assigned files + for (var f in files) { + //each item has a property id and the file object, we'll ensure that the id is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + files[f].id, files[f].file); + } + }, function (data, status, headers, config) { //success callback @@ -153,13 +159,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** saves or updates a content object */ - saveContent: function (content, isNew) { - return saveContentItem(content, "save" + (isNew ? "New" : "")); + saveContent: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); }, /** saves and publishes a content object */ - publishContent: function (content, isNew) { - return saveContentItem(content, "publish" + (isNew ? "New" : "")); + publishContent: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); } }; @@ -248,6 +254,55 @@ function mediaResource($q, $http) { return Umbraco.Sys.ServerVariables.mediaApiBaseUrl + "GetChildren?parentId=" + parentId; } + /** internal method to get the api url for publishing */ + function getSaveUrl() { + return Umbraco.Sys.ServerVariables.mediaApiBaseUrl + "PostSave"; + } + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); + + //save the data + umbRequestHelper.postMultiPartRequest( + getSaveUrl(content.id), + { key: "mediaItem", value: umbDataFormatter.formatContentPostData(content, action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in files) { + //each item has a property id and the file object, we'll ensure that the id is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + files[f].id, files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + deferred.reject('Failed to save data for media id ' + content.id); + }); + + return deferred.promise; + } + return { getById: function (id) { @@ -303,7 +358,12 @@ function mediaResource($q, $http) { }); return deferred.promise; - } + }, + + /** saves or updates a media object */ + saveMedia: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, }; } diff --git a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js index 4f483dbf97..249ceeb849 100644 --- a/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js +++ b/src/Umbraco.Web.UI.Client/build/belle/js/umbraco.services.js @@ -628,7 +628,7 @@ function umbRequestHelper($http) { //call the callback if (transformCallback) { - transformCallback.apply(this, [formData]); + transformCallback.apply(this, [data, formData]); } return formData; 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 98cc086be3..4a4f541b16 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 @@ -18,7 +18,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return Umbraco.Sys.ServerVariables.contentApiBaseUrl + "PostSave"; } /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action) { + function saveContentItem(content, action, files) { var deferred = $q.defer(); //save the active tab id so we can set it when the data is returned. @@ -31,8 +31,14 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.postMultiPartRequest( getSaveUrl(content.id), { key: "contentItem", value: umbDataFormatter.formatContentPostData(content, action) }, - function (data) { - //TODO: transform the request callback and add the files associated with the request + function (data, formData) { + //now add all of the assigned files + for (var f in files) { + //each item has a property id and the file object, we'll ensure that the id is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + files[f].id, files[f].file); + } + }, function (data, status, headers, config) { //success callback @@ -145,13 +151,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** saves or updates a content object */ - saveContent: function (content, isNew) { - return saveContentItem(content, "save" + (isNew ? "New" : "")); + saveContent: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); }, /** saves and publishes a content object */ - publishContent: function (content, isNew) { - return saveContentItem(content, "publish" + (isNew ? "New" : "")); + publishContent: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); } }; 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 f56df531f0..35ecf79223 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 @@ -19,6 +19,55 @@ function mediaResource($q, $http) { return Umbraco.Sys.ServerVariables.mediaApiBaseUrl + "GetChildren?parentId=" + parentId; } + /** internal method to get the api url for publishing */ + function getSaveUrl() { + return Umbraco.Sys.ServerVariables.mediaApiBaseUrl + "PostSave"; + } + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + var deferred = $q.defer(); + + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(content.tabs, activeTab)); + + //save the data + umbRequestHelper.postMultiPartRequest( + getSaveUrl(content.id), + { key: "mediaItem", value: umbDataFormatter.formatContentPostData(content, action) }, + function (data, formData) { + //now add all of the assigned files + for (var f in files) { + //each item has a property id and the file object, we'll ensure that the id is suffixed to the key + // so we know which property it belongs to on the server side + formData.append("file_" + files[f].id, files[f].file); + } + + }, + function (data, status, headers, config) { + //success callback + + //reset the tabs and set the active one + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, + function (data, status, headers, config) { + //failure callback + + deferred.reject('Failed to save data for media id ' + content.id); + }); + + return deferred.promise; + } + return { getById: function (id) { @@ -74,7 +123,12 @@ function mediaResource($q, $http) { }); return deferred.promise; - } + }, + + /** saves or updates a media object */ + saveMedia: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js b/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js index 1882b9f7b3..5821914796 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/utill.service.js @@ -43,7 +43,7 @@ function umbRequestHelper($http) { //call the callback if (transformCallback) { - transformCallback.apply(this, [formData]); + transformCallback.apply(this, [data, formData]); } return formData; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js index aa8ba1ff1a..7c03acb194 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/contentedit.controller.js @@ -16,16 +16,27 @@ angular.module("umbraco") }); } + $scope.files = []; + $scope.addFiles = function (propertyId, files) { + //this will clear the files for the current property and then add the new ones for the current property + $scope.files = _.reject($scope.files, function (item) { + return item.id == propertyId; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the scope's files collection + $scope.files.push({ id: propertyId, file: files[i] }); + } + }; $scope.saveAndPublish = function (cnt) { cnt.publishDate = new Date(); - contentResource.publishContent(cnt); + contentResource.publishContent(cnt, $routeParams.create, $scope.files); notificationsService.success("Published", "Content has been saved and published"); }; $scope.save = function (cnt) { cnt.updateDate = new Date(); - contentResource.saveContent(cnt); + contentResource.saveContent(cnt, $routeParams.create, $scope.files); notificationsService.success("Saved", "Content has been saved"); }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js index 81255adfe4..dc3c0a9a9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/mediaedit.controller.js @@ -14,9 +14,21 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS }); } + $scope.files = []; + $scope.addFiles = function (propertyId, files) { + //this will clear the files for the current property and then add the new ones for the current property + $scope.files = _.reject($scope.files, function (item) { + return item.id == propertyId; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the scope's files collection + $scope.files.push({ id: propertyId, file: files[i] }); + } + }; + $scope.save = function (cnt) { cnt.updateDate = new Date(); - contentResource.saveContent(cnt); + mediaResource.saveMedia(cnt, $routeParams.create, $scope.files); notificationsService.success("Saved", "Media has been saved"); }; } diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest index 745d7c7814..17f9cb6aff 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Package.manifest @@ -1,7 +1,7 @@ { propertyEditors: [ { - id: "30CA72BD-A349-4386-B935-FA532DF24B4B", + id: "5e9b75ae-face-41c8-b47e-5f4b0fd82f83", name: "Rich Text Editor", editor: { view: "~/umbraco/Views/propertyeditors/umbraco/rte/editor.html" diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs index 223060b141..585948c2cf 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/FileUploadPropertyEditor.cs @@ -2,7 +2,7 @@ namespace Umbraco.Web.UI.App_Plugins.MyPackage.PropertyEditors { - [PropertyEditor("23A66468-30E2-4537-8039-625F8BC5CA1E", "File upload", + [PropertyEditor("5032a6e6-69e3-491d-bb28-cd31cd11086c", "File upload", "~/App_Plugins/MyPackage/PropertyEditors/Views/FileUploadEditor.html")] public class FileUploadPropertyEditor : PropertyEditor { diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js index c841478b52..cef520f6c2 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/PropertyEditors/Js/FileUploadEditor.js @@ -8,9 +8,21 @@ define(['namespaceMgr'], function () { MyPackage.PropertyEditors.FileUploadEditor = function ($scope, $element, $compile) { //create the property to show the list of files currently saved - $scope.persistedFiles = _.map(angular.fromJson($scope.model.value), function(item) { - return item.file; - }); + if ($scope.model.value != "") { + + //for legacy data, this will not be an array, just a string so convert to an array + if (!$scope.model.value.startsWith('[')) { + $scope.model.value = "[file: '" + $scope.model.value + "']"; + } + + $scope.persistedFiles = _.map(angular.fromJson($scope.model.value), function (item) { + return item.file; + }); + } + else { + $scope.persistedFiles = []; + } + $scope.clearFiles = false; @@ -29,6 +41,7 @@ define(['namespaceMgr'], function () { //if clear files is selected then we'll clear all the files that are about // to be uploaded if ($scope.clearFiles) { + //TODO: There should be a better way! We don't want to have to know about the parent scope //clear the parent files collection (we don't want to upload any!) $scope.$parent.addFiles($scope.model.id, []); //clear the current files diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 72db8d51a3..9bb6ac0cd9 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -94,11 +94,11 @@ namespace Umbraco.Web.Editors /// Saves content /// /// - [ContentItemValidationFilter] + [ContentItemValidationFilter(typeof(ContentItemValidationHelper))] [FileUploadCleanupFilter] public ContentItemDisplay PostSave( [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) + ContentItemSave contentItem) { //If we've reached here it means: // * Our model has been bound @@ -123,7 +123,7 @@ namespace Umbraco.Web.Editors d.Add("files", files); } var data = new ContentPropertyData(p.Value, d); - + //get the deserialized value from the property editor dboProperty.Value = p.PropertyEditor.ValueEditor.DeserializeValue(data, dboProperty.Value); } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 1ce23d9cfa..2856b97d56 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -2,11 +2,16 @@ using System.Net; using System.Net.Http; using System.Web.Http; +using System.Web.Http.ModelBinding; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Editors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using System.Linq; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors { @@ -68,7 +73,7 @@ namespace Umbraco.Web.Editors /// /// Returns the root media objects /// - public IEnumerable> GetRootMedia() + public IEnumerable> GetRootMedia() { return Services.MediaService.GetRootMedia() .Select(x => _mediaModelMapper.ToMediaItemSimple(x)); @@ -77,10 +82,55 @@ namespace Umbraco.Web.Editors /// /// Returns the child media objects /// - public IEnumerable> GetChildren(int parentId) + public IEnumerable> GetChildren(int parentId) { return Services.MediaService.GetChildren(parentId) .Select(x => _mediaModelMapper.ToMediaItemSimple(x)); } + + /// + /// Saves content + /// + /// + [ContentItemValidationFilter(typeof(ContentItemValidationHelper))] + [FileUploadCleanupFilter] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave mediaItem) + { + //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 + + //Now, we just need to save the data + + //Save the property values + foreach (var p in mediaItem.ContentDto.Properties) + { + //get the dbo property + var dboProperty = mediaItem.PersistedContent.Properties[p.Alias]; + + //create the property data to send to the property editor + var d = new Dictionary(); + //add the files if any + var files = mediaItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray(); + if (files.Any()) + { + d.Add("files", files); + } + var data = new ContentPropertyData(p.Value, d); + + //get the deserialized value from the property editor + dboProperty.Value = p.PropertyEditor.ValueEditor.DeserializeValue(data, dboProperty.Value); + } + + //save the item + Services.MediaService.Save(mediaItem.PersistedContent); + + //return the updated model + return _mediaModelMapper.ToMediaItemDisplay(mediaItem.PersistedContent); + } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 2b9bc11d6e..be12bab4fb 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -11,8 +11,9 @@ namespace Umbraco.Web.Models.ContentEditing /// A model representing a basic content item /// [DataContract(Name = "content", Namespace = "")] - public class ContentItemBasic + public class ContentItemBasic where T: ContentPropertyBasic + where TPersisted : IContentBase { public ContentItemBasic() { @@ -64,7 +65,7 @@ namespace Umbraco.Web.Models.ContentEditing /// The real persisted content object /// [JsonIgnore] - internal IContent PersistedContent { get; set; } + internal TPersisted PersistedContent { get; set; } /// /// The DTO object used to gather all required content data including data type information etc... for use with validation @@ -74,9 +75,9 @@ namespace Umbraco.Web.Models.ContentEditing /// instead of having to look up all the data individually. /// [JsonIgnore] - internal ContentItemDto ContentDto { get; set; } + internal ContentItemDto ContentDto { get; set; } - protected bool Equals(ContentItemBasic other) + protected bool Equals(ContentItemBasic other) { return Id == other.Id; } @@ -85,7 +86,7 @@ namespace Umbraco.Web.Models.ContentEditing { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - var other = obj as ContentItemBasic; + var other = obj as ContentItemBasic; return other != null && Equals(other); } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 2a68e1caf7..0fc93d9502 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -1,13 +1,14 @@ using System; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { /// /// A model representing a content item to be displayed in the back office /// - public class ContentItemDisplay : TabbedContentItem + public class ContentItemDisplay : TabbedContentItem { [DataMember(Name = "publishDate")] diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs index 9e39578fb9..00fb91da2b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDto.cs @@ -1,9 +1,12 @@ -namespace Umbraco.Web.Models.ContentEditing +using Umbraco.Core.Models; + +namespace Umbraco.Web.Models.ContentEditing { /// /// Represents a content item from the database including all of the required data that we need to work with such as data type data /// - internal class ContentItemDto : ContentItemBasic + internal class ContentItemDto : ContentItemBasic + where TPersisted : IContentBase { } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs index e4efea60b0..6d160cbeb8 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs @@ -2,13 +2,20 @@ using System.Runtime.Serialization; using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; +using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { + public interface IHaveUploadedFiles + { + List UploadedFiles { get; } + } + /// /// A model representing a content item to be saved /// - public class ContentItemSave : ContentItemBasic + public class ContentItemSave : ContentItemBasic, IHaveUploadedFiles + where TPersisted : IContentBase { public ContentItemSave() { diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs index 49f1f2bd02..5900aa756d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MediaItemDisplay.cs @@ -1,12 +1,13 @@ using System; using System.Runtime.Serialization; +using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { /// /// A model representing a content item to be displayed in the back office /// - public class MediaItemDisplay : TabbedContentItem + public class MediaItemDisplay : TabbedContentItem { } diff --git a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs index 646cc58842..6e1411f62e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TabbedContentItem.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Newtonsoft.Json; +using Umbraco.Core.Models; namespace Umbraco.Web.Models.ContentEditing { - public abstract class TabbedContentItem : ContentItemBasic - where T : ContentPropertyBasic + public abstract class TabbedContentItem : ContentItemBasic + where T : ContentPropertyBasic where TPersisted : IContentBase { protected TabbedContentItem() { diff --git a/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs index b716c39c4f..ca23f95664 100644 --- a/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs @@ -20,9 +20,10 @@ namespace Umbraco.Web.Models.Mapping ProfileMapper = profileMapper; } - protected ContentItemDto ToContentItemDtoBase(IContentBase content) + protected ContentItemDto ToContentItemDtoBase(IContentBase content) + where TPersisted : IContentBase { - return CreateContent(content, null, (propertyDto, originalProperty, propEditor) => + return CreateContent, ContentPropertyDto, TPersisted>(content, null, (propertyDto, originalProperty, propEditor) => { propertyDto.Alias = originalProperty.Alias; propertyDto.Description = originalProperty.PropertyType.Description; @@ -32,9 +33,10 @@ namespace Umbraco.Web.Models.Mapping }); } - protected ContentItemBasic ToContentItemSimpleBase(IContentBase content) + protected ContentItemBasic ToContentItemSimpleBase(IContentBase content) + where TPersisted : IContentBase { - return CreateContent, ContentPropertyBasic>(content, null, null); + return CreateContent, ContentPropertyBasic, TPersisted>(content, null, null); } protected IList> GetTabs(IContentBase content) @@ -71,14 +73,15 @@ namespace Umbraco.Web.Models.Mapping }); return tabs; - } + } - protected TContent CreateContent(IContentBase content, - Action contentCreatedCallback = null, - Action propertyCreatedCallback = null, - bool createProperties = true) - where TContent : ContentItemBasic, new() - where TContentProperty : ContentPropertyBasic, new() + protected TContent CreateContent(IContentBase content, + Action contentCreatedCallback = null, + Action propertyCreatedCallback = null, + bool createProperties = true) + where TContent : ContentItemBasic, new() + where TContentProperty : ContentPropertyBasic, new() + where TPersisted : IContentBase { var result = new TContent { @@ -110,7 +113,12 @@ namespace Umbraco.Web.Models.Mapping { //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 = GlobalSettings.Path.EnsureEndsWith('/') + "views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html"; + display.View = GlobalSettings.Path.EnsureEndsWith('/') + + "views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html"; + } + else + { + display.View = propEditor.ValueEditor.View; } }); diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 5acaaa8156..e3cb35608e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -15,9 +15,9 @@ namespace Umbraco.Web.Models.Mapping { } - public ContentItemDto ToContentItemDto(IContent content) + public ContentItemDto ToContentItemDto(IContent content) { - var result = base.ToContentItemDtoBase(content); + var result = base.ToContentItemDtoBase(content); //NOTE: we don't need this for the dto and it's an extra lookup //result.ContentTypeAlias = content.ContentType.Alias; //result.Icon = content.ContentType.Icon; @@ -25,9 +25,9 @@ namespace Umbraco.Web.Models.Mapping return result; } - public ContentItemBasic ToContentItemSimple(IContent content) + public ContentItemBasic ToContentItemSimple(IContent content) { - var result = base.ToContentItemSimpleBase(content); + var result = base.ToContentItemSimpleBase(content); result.ContentTypeAlias = content.ContentType.Alias; result.Icon = content.ContentType.Icon; result.Updator = ProfileMapper.ToBasicUser(content.GetWriterProfile()); @@ -39,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping //create the list of tabs for properties assigned to tabs. var tabs = GetTabs(content); - var result = CreateContent(content, (display, originalContent) => + var result = CreateContent(content, (display, originalContent) => { //fill in the rest display.Updator = ProfileMapper.ToBasicUser(content.GetWriterProfile()); diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 3d8fc071d1..a97c249315 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -17,9 +17,9 @@ namespace Umbraco.Web.Models.Mapping { } - public ContentItemDto ToMediaItemDto(IMedia content) + public ContentItemDto ToMediaItemDto(IMedia content) { - var result = base.ToContentItemDtoBase(content); + var result = base.ToContentItemDtoBase(content); //NOTE: we don't need this for the dto and it's an extra lookup //result.ContentTypeAlias = content.ContentType.Alias; //result.Icon = content.ContentType.Icon; @@ -27,9 +27,9 @@ namespace Umbraco.Web.Models.Mapping return result; } - public ContentItemBasic ToMediaItemSimple(IMedia content) + public ContentItemBasic ToMediaItemSimple(IMedia content) { - var result = base.ToContentItemSimpleBase(content); + var result = base.ToContentItemSimpleBase(content); result.ContentTypeAlias = content.ContentType.Alias; result.Icon = content.ContentType.Icon; return result; @@ -40,7 +40,7 @@ namespace Umbraco.Web.Models.Mapping //create the list of tabs for properties assigned to tabs. var tabs = GetTabs(media); - var result = CreateContent(media, (display, originalContent) => + var result = CreateContent(media, (display, originalContent) => { //fill in the rest display.ContentTypeAlias = media.ContentType.Alias; diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index c3be767a7b..074dbaf37d 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.WebApi.Binders /// /// /// - private async Task GetModel(HttpRequestMessage request, MultipartFormDataStreamProvider provider) + private async Task> GetModel(HttpRequestMessage request, MultipartFormDataStreamProvider provider) { //IMPORTANT!!! We need to ensure the umbraco context here because this is running in an async thread UmbracoContext.EnsureContext(request.Properties["MS_HttpContext"] as HttpContextBase, ApplicationContext.Current); @@ -100,7 +100,7 @@ namespace Umbraco.Web.WebApi.Binders var contentItem = result.FormData["contentItem"]; //transform the json into an object - var model = JsonConvert.DeserializeObject(contentItem); + var model = JsonConvert.DeserializeObject>(contentItem); //get the files foreach (var file in result.FileData) diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs index 1b7dda29fd..37dde4c8fd 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationFilterAttribute.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -12,53 +13,35 @@ using Umbraco.Web.Models.Mapping; namespace Umbraco.Web.WebApi.Filters { - /// - /// Validates the content item - /// - /// - /// There's various validation happening here both value validation and structure validation - /// to ensure that malicious folks are not trying to post invalid values or to invalid properties. - /// - internal class ContentItemValidationFilterAttribute : ActionFilterAttribute + internal class ContentItemValidationHelper + where TPersisted : IContentBase { private readonly ApplicationContext _applicationContext; - public ContentItemValidationFilterAttribute(ApplicationContext applicationContext) + public ContentItemValidationHelper(ApplicationContext applicationContext) { _applicationContext = applicationContext; } - public ContentItemValidationFilterAttribute() + public ContentItemValidationHelper() : this(ApplicationContext.Current) { } - /// - /// Returns true so that other filters can execute along with this one - /// - public override bool AllowMultiple + public void ValidateItem(HttpActionContext actionContext) { - get { return true; } - } - - /// - /// Performs the validation - /// - /// - public override void OnActionExecuting(HttpActionContext actionContext) - { - var contentItem = actionContext.ActionArguments["contentItem"] as ContentItemSave; + var contentItem = actionContext.ActionArguments["contentItem"] as ContentItemSave; if (contentItem == null) { - actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(ContentItemSave) + " found in request"); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "No " + typeof(ContentItemSave) + " found in request"); return; } //now do each validation step - if (!ValidateExistingContent(contentItem, actionContext)) return; - if (!ValidateProperties(contentItem, actionContext)) return; - if (!ValidateData(contentItem, actionContext)) return; + if (ValidateExistingContent(contentItem, actionContext) == false) return; + if (ValidateProperties(contentItem, actionContext) == false) return; + if (ValidateData(contentItem, actionContext) == false) return; } /// @@ -67,8 +50,8 @@ namespace Umbraco.Web.WebApi.Filters /// /// /// - private bool ValidateExistingContent(ContentItemSave postedItem, HttpActionContext actionContext) - { + private bool ValidateExistingContent(ContentItemBasic postedItem, HttpActionContext actionContext) + { if (postedItem.PersistedContent == null) { var message = string.Format("content with id: {0} was not found", postedItem.Id); @@ -86,7 +69,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// //private bool ValidateProperties(ContentItemSave postedItem, ContentItemDto realItem, HttpActionContext actionContext) - private bool ValidateProperties(ContentItemSave postedItem, HttpActionContext actionContext) + private bool ValidateProperties(ContentItemBasic postedItem, HttpActionContext actionContext) { foreach (var p in postedItem.Properties) { @@ -105,7 +88,7 @@ namespace Umbraco.Web.WebApi.Filters } //TODO: Validate the property type data - private bool ValidateData(ContentItemSave postedItem, HttpActionContext actionContext) + private bool ValidateData(ContentItemBasic postedItem, HttpActionContext actionContext) { foreach (var p in postedItem.ContentDto.Properties) { @@ -159,6 +142,44 @@ namespace Umbraco.Web.WebApi.Filters return actionContext.ModelState.IsValid; } + } + + /// + /// Validates the content item + /// + /// + /// There's various validation happening here both value validation and structure validation + /// to ensure that malicious folks are not trying to post invalid values or to invalid properties. + /// + internal class ContentItemValidationFilterAttribute : ActionFilterAttribute + { + private readonly Type _helperType; + private dynamic _helper; + + public ContentItemValidationFilterAttribute(Type helperType) + { + _helperType = helperType; + _helper = Activator.CreateInstance(helperType); + } + + /// + /// Returns true so that other filters can execute along with this one + /// + public override bool AllowMultiple + { + get { return true; } + } + + /// + /// Performs the validation + /// + /// + public override void OnActionExecuting(HttpActionContext actionContext) + { + _helper.ValidateItem(actionContext); + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs index 6b909b9c33..91d42fa123 100644 --- a/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FileUploadCleanupFilterAttribute.cs @@ -1,7 +1,8 @@ -using System.IO; -using System.Linq; +using System.Linq; using System.Web.Http.Filters; +using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; +using File = System.IO.File; namespace Umbraco.Web.WebApi.Filters { @@ -24,7 +25,7 @@ namespace Umbraco.Web.WebApi.Filters if (actionExecutedContext.ActionContext.ActionArguments.Any()) { - var contentItem = actionExecutedContext.ActionContext.ActionArguments.First().Value as ContentItemSave; + var contentItem = actionExecutedContext.ActionContext.ActionArguments.First().Value as IHaveUploadedFiles; if (contentItem != null) { //cleanup any files associated