diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index ec7eddbaf6..350ae7be1f 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -19,6 +19,9 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase { + //fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default + private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral; + private static readonly Lazy Ps = new Lazy(); private string _alias; @@ -46,7 +49,7 @@ namespace Umbraco.Core.Models _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _variations = ContentVariation.InvariantNeutral; + _variations = DefaultVaryBy; } protected ContentTypeBase(IContentTypeBase parent) @@ -67,7 +70,7 @@ namespace Umbraco.Core.Models _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _variations = ContentVariation.InvariantNeutral; + _variations = DefaultVaryBy; } /// diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index 0cefaf2ccc..7e5254201f 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -167,9 +167,7 @@ namespace Umbraco.Core.Models /// The property. /// The property value. /// The datatype configuration. - /// The property editor tags configuration attribute. - /// - /// The tags configuration is specified by the marking the property editor. + /// /// The value is either a string (delimited string) or an enumeration of strings (tag list). /// This is used both by the content repositories to initialize a property with some tag values, and by the /// content controllers to update a property with values received from the property editor. diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index b7589d71ff..0ab30eabf6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -19,7 +19,10 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public class PropertyType : EntityBase, IEquatable - { + { + //fixme this should be invariant by default but for demo purposes and until the UI is updated to support changing a property type we'll make this neutral by default + private const ContentVariation DefaultVaryBy = ContentVariation.CultureNeutral; + private static PropertySelectors _selectors; private readonly bool _forceValueStorageType; @@ -47,7 +50,7 @@ namespace Umbraco.Core.Models _propertyEditorAlias = dataType.EditorAlias; _valueStorageType = dataType.DatabaseType; - _variations = ContentVariation.InvariantNeutral; + _variations = DefaultVaryBy; } /// @@ -84,7 +87,7 @@ namespace Umbraco.Core.Models _valueStorageType = valueStorageType; _forceValueStorageType = forceValueStorageType; _alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias); - _variations = ContentVariation.InvariantNeutral; + _variations = DefaultVaryBy; } private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); @@ -391,7 +394,9 @@ namespace Umbraco.Core.Models { throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); } - + + + //fixme - perhaps this and other validation methods should be a service level (not a model) thing? /// /// Determines whether a value is valid for this property type. /// diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 5aba2a5e0e..2d0b34a849 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -19,8 +19,7 @@ namespace Umbraco.Core.PropertyEditors public class DataEditor : IDataEditor { private IDictionary _defaultConfiguration; - private IDataValueEditor _valueEditorAssigned; - + /// /// Initializes a new instance of the class. /// @@ -160,10 +159,6 @@ namespace Umbraco.Core.PropertyEditors /// protected virtual IDataValueEditor CreateValueEditor() { - // handle assigned editor, or create a new one - if (_valueEditorAssigned != null) - return _valueEditorAssigned; - if (Attribute == null) throw new InvalidOperationException("The editor does not specify a view."); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index 60912edad0..ea95f8708f 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -232,22 +232,24 @@ namespace Umbraco.Core.PropertyEditors // eg // [ { "value": "hello" }, { "lang": "fr-fr", "value": "bonjour" } ] - /// - /// A method to deserialize the string value that has been saved in the content editor - /// to an object to be stored in the database. - /// - /// - /// - /// The current value that has been persisted to the database for this editor. This value may be usesful for - /// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used. - /// + /// + /// A method to deserialize the string value that has been saved in the content editor + /// to an object to be stored in the database. + /// + /// + /// + /// The current value that has been persisted to the database for this editor. This value may be usesful for + /// how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used. + /// + /// + /// /// - /// - /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. - /// - /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the - /// value to the DB will fail when it tries to validate the value type. - /// + /// + /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. + /// + /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the + /// value to the DB will fail when it tries to validate the value type. + /// public virtual object FromEditor(ContentPropertyData editorValue, object currentValue) { //if it's json but it's empty json, then return null @@ -269,16 +271,18 @@ namespace Umbraco.Core.PropertyEditors /// A method used to format the database value to a value that can be used by the editor /// /// - /// /// + /// + /// /// /// /// The object returned will automatically be serialized into json notation. For most property editors /// the value returned is probably just a string but in some cases a json structure will be returned. /// - public virtual object ToEditor(Property property, IDataTypeService dataTypeService) + public virtual object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.GetValue() == null) return string.Empty; + var val = property.GetValue(languageId, segment); + if (val == null) return string.Empty; switch (ValueTypes.ToStorageType(ValueType)) { @@ -286,7 +290,7 @@ namespace Umbraco.Core.PropertyEditors case ValueStorageType.Nvarchar: //if it is a string type, we will attempt to see if it is json stored data, if it is we'll try to convert //to a real json object so we can pass the true json object directly to angular! - var asString = property.GetValue().ToString(); + var asString = val.ToString(); if (asString.DetectIsJson()) { try @@ -304,12 +308,12 @@ namespace Umbraco.Core.PropertyEditors case ValueStorageType.Decimal: //Decimals need to be formatted with invariant culture (dots, not commas) //Anything else falls back to ToString() - var decim = property.GetValue().TryConvertTo(); + var decim = val.TryConvertTo(); return decim.Success ? decim.Result.ToString(NumberFormatInfo.InvariantInfo) - : property.GetValue().ToString(); + : val.ToString(); case ValueStorageType.Date: - var date = property.GetValue().TryConvertTo(); + var date = val.TryConvertTo(); if (date.Success == false || date.Result == null) { return string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index 5b419d0ec8..9e31f94121 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Converts a property value to a value for the editor. /// - object ToEditor(Property property, IDataTypeService dataTypeService); + object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null); // fixme - editing - document or remove these // why property vs propertyType? services should be injected! etc... diff --git a/src/Umbraco.Core/Services/LocalizationServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizationServiceExtensions.cs new file mode 100644 index 0000000000..b317b91cd5 --- /dev/null +++ b/src/Umbraco.Core/Services/LocalizationServiceExtensions.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public static class LocalizationServiceExtensions + { + /// + /// Returns the configured default variant language + /// + /// + /// + public static ILanguage GetDefaultVariantLanguage(this ILocalizationService service) + { + var langs = service.GetAllLanguages().OrderBy(x => x.Id).ToList(); + + //if there's only one language, by default it is the default + if (langs.Count == 1) + { + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + return langs[0]; + } + + if (langs.All(x => !x.IsDefaultVariantLanguage)) + { + //if no language has the default flag, then the defaul language is the one with the lowest id + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + return langs[0]; + } + + return langs.First(x => x.IsDefaultVariantLanguage); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 249fa7e1a9..67892bac01 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1415,6 +1415,7 @@ + diff --git a/src/Umbraco.Examine/UmbracoContentIndexer.cs b/src/Umbraco.Examine/UmbracoContentIndexer.cs index 2f177e2466..0c02ce02b9 100644 --- a/src/Umbraco.Examine/UmbracoContentIndexer.cs +++ b/src/Umbraco.Examine/UmbracoContentIndexer.cs @@ -347,6 +347,7 @@ namespace Umbraco.Examine foreach (var property in c.Properties) { //only add the value if its not null or empty (we'll check for string explicitly here too) + //fixme support variants with language id var val = property.GetValue(); switch (val) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index ead0f2be27..6959ccc4e2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -42,6 +42,11 @@ } } + //init can be called more than once and we don't want to have multiple bound events + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) { createButtons(args.node); })); @@ -97,20 +102,30 @@ $scope.content.apps[0].active = true; // create new editor for split view - if($scope.editors.length === 0) { - var editor = {}; - editor.content = $scope.content; - $scope.editors.push(editor); - } - + if ($scope.editors.length === 0) { + var editor = { + content: $scope.content + }; + $scope.editors.push(editor); + } + else if ($scope.editors.length === 1) { + $scope.editors[0].content = $scope.content + } + else { + //fixme - need to fix something here if we are re-loading a content item that is in a split view + } } - function getNode() { + /** + * This does the content loading and initializes everything, called on load and changing variants + * @param {any} languageId + */ + function getNode(languageId) { $scope.page.loading = true; //we are editing so get the content item from the server - $scope.getMethod()($scope.contentId) + $scope.getMethod()($scope.contentId, languageId) .then(function (data) { $scope.content = data; @@ -392,7 +407,11 @@ angular.forEach(variants, function(variant) { variant.current = false; }); + selectedVariant.current = true; + + //go get the variant + getNode(selectedVariant.language.id); } $scope.closeSplitView = function(index, editor) { @@ -419,6 +438,7 @@ }, 100); // fake loading of content + // TODO: Make this real, but how do we deal with saving since currently we only save one variant at a time?! $timeout(function(){ $scope.editors[editorIndex].content = angular.copy($scope.content); $scope.editors[editorIndex].content.name = "What a variant"; 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 642e4fb13b..7d569f7622 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 @@ -317,13 +317,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the content item. * */ - getById: function (id) { + getById: function (id, languageId) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }])), + { id: id, languageId: languageId })), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 78dec2f065..a6b77ce65f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -324,6 +324,16 @@ //this is basically the same as for media but we need to explicitly add some extra properties var saveModel = this.formatMediaPostData(displayModel, action); + //get the selected variant + var currVariant = _.find(displayModel.variants, + function(v) { + return v.current === true; + }); + if (currVariant) { + saveModel.languageId = currVariant.language.id; + } + + var propExpireDate = displayModel.removeDate; var propReleaseDate = displayModel.releaseDate; var propTemplate = displayModel.template; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b23a4af763..9f1bf27cb9 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -25,8 +25,10 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Web.PublishedCache; using Umbraco.Core.Events; using Umbraco.Core.Models.Validation; +using Umbraco.Web.Models; using Umbraco.Web._Legacy.Actions; using Constants = Umbraco.Core.Constants; +using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Editors { @@ -71,7 +73,7 @@ namespace Umbraco.Web.Editors public IEnumerable GetByIds([FromUri]int[] ids) { var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(x => ContextMapper.Map(x, UmbracoContext)); + return foundContent.Select(x => MapToDisplay(x)); } /// @@ -223,7 +225,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = ContextMapper.Map(foundContent, UmbracoContext); + var content = MapToDisplay(foundContent); SetupBlueprint(content, foundContent); @@ -250,18 +252,20 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id /// /// + /// /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) + public ContentItemDisplay GetById(int id, int? languageId = null) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) { HandleContentNotFound(id); + return null;//irrelevant since the above throws } - var content = ContextMapper.Map(foundContent, UmbracoContext); + var content = MapToDisplay(foundContent, languageId); return content; } @@ -274,7 +278,7 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); } - var content = ContextMapper.Map(foundContent, UmbracoContext); + var content = MapToDisplay(foundContent); return content; } @@ -297,7 +301,7 @@ namespace Umbraco.Web.Editors } var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); - var mapped = ContextMapper.Map(emptyContent, UmbracoContext); + var mapped = MapToDisplay(emptyContent); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); @@ -536,7 +540,16 @@ namespace Umbraco.Web.Editors [OutgoingEditorModelEvent] public ContentItemDisplay PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { - return PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + var contentItemDisplay = PostSaveInternal(contentItem, content => Services.ContentService.Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + //ensure the active language is still selected + if (contentItem.LanguageId.HasValue) + { + foreach (var contentVariation in contentItemDisplay.Variants) + { + contentVariation.IsCurrent = contentVariation.Language.Id == contentItem.LanguageId; + } + } + return contentItemDisplay; } private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func saveMethod) @@ -561,7 +574,7 @@ namespace Umbraco.Web.Editors { //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 = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); + var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); @@ -598,13 +611,13 @@ namespace Umbraco.Web.Editors else { //publish the item and check if it worked, if not we will show a diff msg below - contentItem.PersistedContent.PublishValues(); // fixme variants? + contentItem.PersistedContent.PublishValues(contentItem.LanguageId); publishStatus = Services.ContentService.SaveAndPublish(contentItem.PersistedContent, Security.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedCancelledByEvent; } //return the updated model - var display = ContextMapper.Map(contentItem.PersistedContent, UmbracoContext); + var display = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -858,7 +871,7 @@ namespace Umbraco.Web.Editors var unpublishResult = Services.ContentService.Unpublish(foundContent, Security.CurrentUser.Id); - var content = ContextMapper.Map(foundContent, UmbracoContext); + var content = MapToDisplay(foundContent); if (unpublishResult.Success == false) { @@ -903,7 +916,10 @@ namespace Umbraco.Web.Editors } } - base.MapPropertyValues(contentItem); + base.MapPropertyValues( + contentItem, + (save, property) => property.GetValue(save.LanguageId), //get prop val + (save, property, v) => property.SetValue(v, save.LanguageId)); //set prop val } /// @@ -1089,6 +1105,25 @@ namespace Umbraco.Web.Editors } return allowed; } + + /// + /// Used to map an instance to a and ensuring a language is present if required + /// + /// + /// + /// + private ContentItemDisplay MapToDisplay(IContent content, int? languageId = null) + { + //a language must be if this content item has any property type that can be varied by language + if (!languageId.HasValue && content.HasLanguageVariantPropertyType()) + { + languageId = Services.LocalizationService.GetDefaultVariantLanguage().Id; + } + var display = ContextMapper.Map(content, UmbracoContext, + new Dictionary { { ContextMapper.LanguageKey, languageId } }); + + return display; + } } } diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 5e1ceb878a..3e73310a6a 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -66,9 +66,16 @@ namespace Umbraco.Web.Editors /// Maps the dto property values to the persisted model /// /// + /// /// - protected virtual void MapPropertyValues(ContentBaseItemSave contentItem) - where TPersisted : IContentBase + /// + /// + protected void MapPropertyValues( + TSaved contentItem, + Func getPropertyValue, + Action savePropertyValue) + where TPersisted : IContentBase + where TSaved : ContentBaseItemSave { // map the property values foreach (var propertyDto in contentItem.ContentDto.Properties) @@ -102,7 +109,7 @@ namespace Umbraco.Web.Editors }; // let the editor convert the value that was received, deal with files, etc - var value = valueEditor.FromEditor(data, property.GetValue()); + var value = valueEditor.FromEditor(data, getPropertyValue(contentItem, property)); // set the value - tags are special var tagAttribute = propertyDto.PropertyEditor.GetTagAttribute(); @@ -110,10 +117,11 @@ namespace Umbraco.Web.Editors { var tagConfiguration = ConfigurationEditor.ConfigurationAs(propertyDto.DataType.Configuration); if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = tagAttribute.Delimiter; + //fixme how is this supposed to work with variants? property.SetTagsValue(value, tagConfiguration); } else - property.SetValue(value); + savePropertyValue(contentItem, property, value); } } diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index e2439e0afc..8bf9d70a12 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -7,11 +7,12 @@ using System.Net.Http; using System.Web.Http; using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Web.Models; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Editors { @@ -42,23 +43,9 @@ namespace Umbraco.Web.Editors [HttpGet] public IEnumerable GetAllLanguages() { - var allLanguages = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - var langs = Mapper.Map>(allLanguages).ToList(); + var allLanguages = Services.LocalizationService.GetAllLanguages(); - //if there's only one language, by default it is the default - if (langs.Count == 1) - { - langs[0].IsDefaultVariantLanguage = true; - langs[0].Mandatory = true; - } - else if (allLanguages.All(x => !x.IsDefaultVariantLanguage)) - { - //if no language has the default flag, then the defaul language is the one with the lowest id - langs[0].IsDefaultVariantLanguage = true; - langs[0].Mandatory = true; - } - - return langs.OrderBy(x => x.Name); + return Mapper.Map, IEnumerable>(allLanguages); } [HttpGet] diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index de472baf56..2c7a5f30c6 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -470,7 +470,11 @@ namespace Umbraco.Web.Editors // * we have a reference to the DTO object and the persisted object // * Permissions are valid - MapPropertyValues(contentItem); + UpdateName(contentItem); + MapPropertyValues( + contentItem, + (save, property) => property.GetValue(), //get prop val + (save, property, v) => property.SetValue(v)); //set prop val //We need to manually check the validation results here because: // * We still need to save the entity even if there are validation value errors @@ -529,19 +533,7 @@ namespace Umbraco.Web.Editors 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 /// diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 39c9f95782..bf60560574 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -379,7 +379,10 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.Username = contentItem.Username; //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); + base.MapPropertyValues( + contentItem, + (save, property) => property.GetValue(), //get prop val + (save, property, v) => property.SetValue(v)); //set prop val } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs index c05f7e9dba..d8ab8149dc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs @@ -10,7 +10,13 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataContract(Name = "content", Namespace = "")] public class ContentItemSave : ContentBaseItemSave - { + { + /// + /// The language Id for the content variation being saved + /// + [DataMember(Name = "languageId")] + public int? LanguageId { get; set; } + /// /// The template alias to save /// diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index defd7cccc9..befada9020 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -11,6 +11,23 @@ namespace Umbraco.Web.Models { public static class ContentExtensions { + /// + /// Returns true if the content has any property type that allows language variants + /// + /// + /// + public static bool HasLanguageVariantPropertyType(this IContent content) + { + return content.PropertyTypes.Any(x => x.Variations == ContentVariation.CultureNeutral); + } + + /// + /// Returns true if the content has a variation for the language/segment combination + /// + /// + /// + /// + /// public static bool HasVariation(this IContent content, int langId, string segment = null) { //TODO: Wire up with new APIs diff --git a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs deleted file mode 100644 index 9b0ae66742..0000000000 --- a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using AutoMapper; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Extends AutoMapper's class to handle Umbraco's context. - /// - internal static class ContextMapper - { - private const string UmbracoContextKey = "ContextMapper.UmbracoContext"; - - public static TDestination Map(TSource obj, UmbracoContext umbracoContext) - => Mapper.Map(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext); - - public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true) - { - if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext) - return umbracoContext; - - // fixme - not a good idea at all - // because this falls back to magic singletons - // so really we should remove this line, but then some tests+app breaks ;( - return Umbraco.Web.Composing.Current.UmbracoContext; - - // better fail fast - if (throwIfMissing) - throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext."); - - return null; - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index aff472d306..83af05c464 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -3,9 +3,11 @@ using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Models.Mapping { @@ -15,10 +17,14 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : ITypeConverter where TDestination : ContentPropertyBasic, new() { + private readonly ILogger _logger; + private readonly PropertyEditorCollection _propertyEditors; protected IDataTypeService DataTypeService { get; } - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) { + _logger = logger; + _propertyEditors = propertyEditors; DataTypeService = dataTypeService; } @@ -28,19 +34,28 @@ namespace Umbraco.Web.Models.Mapping /// public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context) { - var editor = Current.PropertyEditors[property.PropertyType.PropertyEditorAlias]; + var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) { - Current.Logger.Error>( + _logger.Error>( "No property editor found, converting to a Label", new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist")); - editor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; + editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; } + + var languageId = context.GetLanguageId(); + + if (!languageId.HasValue && property.PropertyType.Variations == ContentVariation.CultureNeutral) + { + //a language Id needs to be set for a property type that can be varried by language + throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}"); + } + var result = new TDestination { Id = property.Id, - Value = editor.GetValueEditor().ToEditor(property, DataTypeService), + Value = editor.GetValueEditor().ToEditor(property, DataTypeService, languageId), 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 c3a5750078..51a2dec819 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -3,6 +3,7 @@ using System.Linq; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -17,8 +18,8 @@ namespace Umbraco.Web.Models.Mapping { private readonly ILocalizedTextService _textService; - public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService) - : base(dataTypeService) + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, logger, propertyEditors) { _textService = textService; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 66f42fc2ad..11ecc518c1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -1,6 +1,8 @@ using System; using AutoMapper; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -11,8 +13,8 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter { - public ContentPropertyDtoConverter(IDataTypeService dataTypeService) - : base(dataTypeService) + public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, logger, propertyEditors) { } public override ContentPropertyDto Convert(Property originalProperty, ContentPropertyDto dest, ResolutionContext context) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs index 7764613a0e..1b9b04e49a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -11,11 +13,11 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyMapperProfile : Profile { - public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService) + public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) { - var contentPropertyBasicConverter = new ContentPropertyBasicConverter(dataTypeService); - var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService); - var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService); + var contentPropertyBasicConverter = new ContentPropertyBasicConverter(dataTypeService, logger, propertyEditors); + var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService, logger, propertyEditors); + var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService, logger, propertyEditors); //FROM Property TO ContentPropertyBasic CreateMap>() @@ -25,13 +27,13 @@ namespace Umbraco.Web.Models.Mapping .ForMember(tab => tab.Alias, expression => expression.Ignore()); //FROM Property TO ContentPropertyBasic - CreateMap().ConvertUsing(contentPropertyBasicConverter); + CreateMap().ConvertUsing((property, basic, arg3) => contentPropertyBasicConverter.Convert(property, basic, arg3)); //FROM Property TO ContentPropertyDto CreateMap().ConvertUsing(contentPropertyDtoConverter); //FROM Property TO ContentPropertyDisplay - CreateMap().ConvertUsing(contentPropertyDisplayConverter); + CreateMap().ConvertUsing((property, basic, arg3) => contentPropertyDisplayConverter.Convert(property, basic, arg3)); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs new file mode 100644 index 0000000000..b46cd219d8 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using AutoMapper; +using ClientDependency.Core; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Extends AutoMapper's class to handle Umbraco's context and other contextual data + /// + internal static class ContextMapper + { + public const string UmbracoContextKey = "ContextMapper.UmbracoContext"; + public const string LanguageKey = "ContextMapper.LanguageId"; + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext) + => Mapper.Map(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext); + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext, IDictionary contextVals) + => Mapper.Map(obj, opt => + { + //set the umb ctx + opt.Items[UmbracoContextKey] = umbracoContext; + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, UmbracoContext umbracoContext, object contextVals) + => Mapper.Map(obj, opt => + { + //set the umb ctx + opt.Items[UmbracoContextKey] = umbracoContext; + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals.ToDictionary()) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, IDictionary contextVals) + => Mapper.Map(obj, opt => + { + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + public static TDestination Map(TSource obj, object contextVals) + => Mapper.Map(obj, opt => + { + //set other supplied context vals + if (contextVals != null) + { + foreach (var contextVal in contextVals.ToDictionary()) + { + opt.Items[contextVal.Key] = contextVal.Value; + } + } + }); + + /// + /// Returns the language Id in the mapping context if one is found + /// + /// + /// + public static int? GetLanguageId(this ResolutionContext resolutionContext) + { + if (!resolutionContext.Options.Items.TryGetValue(LanguageKey, out var obj)) return null; + + if (obj is int i) + return i; + + return null; + } + + /// + /// Returns the in the mapping context if one is found + /// + /// + /// + /// + public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true) + { + if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext) + return umbracoContext; + + // better fail fast + if (throwIfMissing) + throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext."); + + // fixme - not a good idea at all + // because this falls back to magic singletons + // so really we should remove this line, but then some tests+app breaks ;( + return Umbraco.Web.Composing.Current.UmbracoContext; + } + } +} + diff --git a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs index 6b9a64d60a..b00a0949ec 100644 --- a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs @@ -1,4 +1,6 @@ -using System.Globalization; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using AutoMapper; using Umbraco.Core.Models; using Umbraco.Web.Models.ContentEditing; @@ -12,6 +14,38 @@ namespace Umbraco.Web.Models.Mapping { CreateMap() .ForMember(l => l.Name, expression => expression.MapFrom(x => x.CultureInfo.DisplayName)); + + CreateMap, IEnumerable>() + .ConvertUsing(); + + } + + /// + /// Converts a list of to a list of and ensures the correct order and defaults are set + /// + // ReSharper disable once ClassNeverInstantiated.Local + private class LanguageCollectionTypeConverter : ITypeConverter, IEnumerable> + { + public IEnumerable Convert(IEnumerable source, IEnumerable destination, ResolutionContext context) + { + var allLanguages = source.OrderBy(x => x.Id).ToList(); + var langs = new List(allLanguages.Select(x => context.Mapper.Map(x, null, context))); + + //if there's only one language, by default it is the default + if (langs.Count == 1) + { + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + else if (allLanguages.All(x => !x.IsDefaultVariantLanguage)) + { + //if no language has the default flag, then the defaul language is the one with the lowest id + langs[0].IsDefaultVariantLanguage = true; + langs[0].Mandatory = true; + } + + return langs.OrderBy(x => x.Name); + } } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs index 5066c4420e..66fb3619cf 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs @@ -169,10 +169,11 @@ namespace Umbraco.Web.Models.Mapping /// /// /// + /// /// - protected override List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) + protected override List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties, ResolutionContext context) { - var result = base.MapProperties(umbracoContext, content, properties); + var result = base.MapProperties(umbracoContext, content, properties, context); var member = (IMember)content; var memberType = member.ContentType; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 9a96cfefae..4102c3fe90 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -152,18 +152,19 @@ namespace Umbraco.Web.Models.Mapping /// /// /// + /// /// /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// - protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs) + protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List> tabs, ResolutionContext context) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab var noGroupProperties = content.GetNonGroupedProperties() .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored .ToList(); - var genericproperties = MapProperties(umbracoContext, content, noGroupProperties); + var genericproperties = MapProperties(umbracoContext, content, noGroupProperties, context); tabs.Add(new Tab { @@ -212,12 +213,16 @@ namespace Umbraco.Web.Models.Mapping /// /// /// + /// /// - protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties) - { - var result = Mapper.Map, IEnumerable>( + protected virtual List MapProperties(UmbracoContext umbracoContext, IContentBase content, List properties, ResolutionContext context) + { + //we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588 + var result = context.Mapper.Map, IEnumerable>( // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. - properties.OrderBy(prop => prop.PropertyType.SortOrder)) + properties.OrderBy(prop => prop.PropertyType.SortOrder), + null, + context) .ToList(); return result; @@ -265,7 +270,7 @@ namespace Umbraco.Web.Models.Mapping continue; //map the properties - var mappedProperties = MapProperties(umbracoContext, source, properties); + var mappedProperties = MapProperties(umbracoContext, source, properties, context); // add the tab // we need to pick an identifier... there is no "right" way... @@ -283,7 +288,7 @@ namespace Umbraco.Web.Models.Mapping }); } - MapGenericProperties(umbracoContext, source, tabs); + MapGenericProperties(umbracoContext, source, tabs, context); // activate the first tab, if any if (tabs.Count > 0) diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs index 94588f722e..b7b29207bc 100644 --- a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.Mapping var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); if (allLanguages.Count == 0) return Enumerable.Empty(); //there's only 1 language defined so we don't have language variants enabled - var langs = Mapper.Map>(allLanguages).ToList(); + var langs = context.Mapper.Map, IEnumerable>(allLanguages, null, context); var variants = langs.Select(x => new ContentVariation { Language = x, @@ -33,26 +33,14 @@ namespace Umbraco.Web.Models.Mapping ExpireDate = source.ExpireDate, PublishDate = source.PublishDate, ReleaseDate = source.ReleaseDate, - Exists = source.HasVariation(x.Id), + Exists = source.HasVariation(x.Id), //TODO: This needs to be wired up with new APIs when they are ready PublishedState = source.PublishedState.ToString() }).ToList(); - //if there's only one language, by default it is the default - if (langs.Count == 1) - { - langs[0].IsDefaultVariantLanguage = true; - langs[0].Mandatory = true; - } - else if (allLanguages.All(x => !x.IsDefaultVariantLanguage)) - { - //if no language has the default flag, then the defaul language is the one with the lowest id - langs[0].IsDefaultVariantLanguage = true; - langs[0].Mandatory = true; - } + var langId = context.GetLanguageId(); - //TODO: Not sure if this is required right now, IsCurrent could purely be a UI thing, we'll see - //set the 'current' - variants.First(x => x.Language.IsDefaultVariantLanguage).IsCurrent = true; + //set the current variant being edited to the one found in the context or the default, whichever matches + variants.First(x => (langId.HasValue && langId.Value == x.Language.Id) || x.Language.IsDefaultVariantLanguage).IsCurrent = true; return variants; } diff --git a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs index 728b4474a8..31425fdadd 100644 --- a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs @@ -18,9 +18,9 @@ namespace Umbraco.Web.PropertyEditors Validators.Add(new DateTimeValidator()); } - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - var date = property.GetValue().TryConvertTo(); + var date = property.GetValue(languageId, segment).TryConvertTo(); if (date.Success == false || date.Result == null) { return String.Empty; @@ -29,4 +29,4 @@ namespace Umbraco.Web.PropertyEditors return date.Result.Value.ToString("yyyy-MM-dd"); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index e666b04abd..9a903b932d 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -59,7 +59,8 @@ namespace Umbraco.Web.PropertyEditors return false; if (ensureValue == false) return true; - return property.GetValue() is string && string.IsNullOrWhiteSpace((string) property.GetValue()) == false; + var val = property.GetValue(); + return val is string s && string.IsNullOrWhiteSpace(s) == false; } /// diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index eb1af95def..af9ae714d1 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -32,17 +32,17 @@ namespace Umbraco.Web.PropertyEditors /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { + var val = property.GetValue(languageId, segment); ImageCropperValue value; try { - // fixme - ignoring variants here?! - value = JsonConvert.DeserializeObject(property.GetValue().ToString()); + value = JsonConvert.DeserializeObject(val.ToString()); } catch { - value = new ImageCropperValue { Src = property.GetValue().ToString() }; + value = new ImageCropperValue { Src = val.ToString() }; } var dataType = dataTypeService.GetDataType(property.PropertyType.DataTypeId); diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index 76c334f254..a6373bd87d 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -78,18 +78,18 @@ namespace Umbraco.Web.PropertyEditors /// cannot have 2 way binding, so to get around that each item in the array needs to be an object with a string. /// /// - /// /// + /// + /// /// /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - return property.GetValue() == null - ? new JObject[] {} - : property.GetValue().ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => JObject.FromObject(new {value = x})); + var val = property.GetValue(languageId, segment); + return val?.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries) + .Select(x => JObject.FromObject(new {value = x})) ?? new JObject[] { }; } diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 95121e8b02..52933e5846 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -139,12 +139,13 @@ namespace Umbraco.Web.PropertyEditors // note: there is NO variant support here - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.GetValue() == null || string.IsNullOrWhiteSpace(property.GetValue().ToString())) + var val = property.GetValue(languageId, segment); + if (val == null || string.IsNullOrWhiteSpace(val.ToString())) return string.Empty; - var value = JsonConvert.DeserializeObject>(property.GetValue().ToString()); + var value = JsonConvert.DeserializeObject>(val.ToString()); if (value == null) return string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 803c8b5994..6cd9675f67 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -69,12 +69,13 @@ namespace Umbraco.Web.PropertyEditors /// Override so that we can return a json array to the editor for multi-select values /// /// - /// /// + /// + /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - var delimited = base.ToEditor(property, dataTypeService).ToString(); + var delimited = base.ToEditor(property, dataTypeService, languageId, segment).ToString(); return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 56fd24bb8a..181471b315 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -60,15 +60,17 @@ namespace Umbraco.Web.PropertyEditors /// Format the data for the editor /// /// - /// /// + /// + /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.GetValue() == null) + var val = property.GetValue(languageId, segment); + if (val == null) return null; - var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(property.GetValue().ToString(), new Dictionary()); + var parsed = MacroTagParser.FormatRichTextPersistedDataForEditor(val.ToString(), new Dictionary()); return parsed; } diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index 8c056f5ff7..f860a5abe7 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -19,21 +19,24 @@ namespace Umbraco.Web.PropertyEditors /// A method used to format the database value to a value that can be used by the editor /// /// - /// /// + /// + /// /// /// /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown /// - public override object ToEditor(Property property, IDataTypeService dataTypeService) + public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) { - if (property.GetValue() == null) return string.Empty; + var val = property.GetValue(languageId, segment); + + if (val == null) return string.Empty; switch (ValueTypes.ToStorageType(ValueType)) { case ValueStorageType.Ntext: case ValueStorageType.Nvarchar: - return property.GetValue().ToString(); + return val.ToString(); case ValueStorageType.Integer: case ValueStorageType.Decimal: case ValueStorageType.Date: diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a060acdef4..6c264c8e83 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -246,7 +246,7 @@ - + diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index 16fbf9b9db..77eb7d7f8a 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; @@ -26,7 +27,10 @@ namespace Umbraco.Web.WebApi.Binders protected override ContentItemDto MapFromPersisted(ContentItemSave model) { - return Mapper.Map>(model.PersistedContent); + return ContextMapper.Map>(model.PersistedContent, new Dictionary + { + [ContextMapper.LanguageKey] = model.LanguageId + }); } } }