diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 7d8476989a..b23efd6bb8 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -86,7 +86,7 @@ /// /// alias for the packages tree /// - public const string Packages = "packager"; + public const string Packages = "packages"; /// /// alias for the dictionary tree. diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 709c37daaa..7e53eb764a 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -271,6 +271,10 @@ namespace Umbraco.Core.Models /// public void SetValue(object value, string culture = null, string segment = null) { + if (PropertyType.Variations == ContentVariation.InvariantNeutral) + { + culture = null; + } PropertyType.ValidateVariation(culture, segment, true); (var pvalue, var change) = GetPValue(culture, segment, true); diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index 58fde70467..e13419f0d6 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -73,7 +73,10 @@ namespace Umbraco.Core.PropertyEditors // but only keep entries that have a non-null/empty value // rest will fall back to default during ToConfigurationEditor() - var keys = editorValues.Where(x => x.Value == null || x.Value is string stringValue && string.IsNullOrWhiteSpace(stringValue)).Select(x => x.Key); + var keys = editorValues.Where(x => + x.Value == null || x.Value is string stringValue && string.IsNullOrWhiteSpace(stringValue)) + .Select(x => x.Key).ToList(); + foreach (var key in keys) editorValues.Remove(key); return editorValues; diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index e4fcfc46f8..a6f2b9b5ec 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -266,7 +266,7 @@ namespace Umbraco.Tests.Routing var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); + SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); var publishedRouter = CreatePublishedRouter(Container); @@ -316,7 +316,7 @@ namespace Umbraco.Tests.Routing var globalSettings = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container globalSettings.Setup(x => x.HideTopLevelNodeFromPath).Returns(false); - SettingsForTests.ConfigureSettings(globalSettings.Object); + SettingsForTests.ConfigureSettings(globalSettings.Object); var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings.Object); var publishedRouter = CreatePublishedRouter(Container); @@ -336,8 +336,5 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, frequest.Culture.Name); Assert.AreEqual(frequest.PublishedContent.Id, expectedNode); } - - - } } 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 124d971b28..882aed2c39 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 @@ -4,7 +4,7 @@ function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, - keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource, overlayService) { + keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource, overlayService, localizationService) { var evts = []; @@ -303,7 +303,7 @@ //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { var dialog = { - title: "Ready to Publish?", //TODO: localize + title: localizationService.localize("content_readyToPublish"), view: "publish", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation @@ -399,7 +399,7 @@ var target = null; var error = { headline: "Cannot automatically restore this item", content: "Use the Move menu item to move it manually" }; - if (data.length == 0) { + if (data.length === 0) { notificationsService.error(error.headline, "There is no 'restore' relation found for this node. Use the Move menu item to move it manually."); $scope.page.buttonRestore = "error"; return; @@ -407,7 +407,7 @@ relation = data[0]; - if (relation.parentId == -1) { + if (relation.parentId === -1) { target = { id: -1, name: "Root" }; moveNode(content, target); } else { @@ -464,7 +464,6 @@ $scope.editors[editorIndex].content = angular.copy($scope.content); $scope.editors[editorIndex].content.name = "What a variant"; // set selected variant on split view content - console.log($scope.editors[editorIndex].content.variants); angular.forEach($scope.editors[editorIndex].content.variants, function (variant) { if (variant.culture === selectedVariant.culture) { variant.current = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 8bfcdfcc5a..2b0fd341d3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -166,6 +166,15 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { 'Failed to retrieve all content types'); }, + allowsVariation: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "AllowsVariation")), + 'Failed to retrieve variant content types'); + }, + getScaffold: function (parentId) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js index 5c3e6eb4c8..034f2c5a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js @@ -7,6 +7,10 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var contentTypeHelperService = { + allowsVariation: function() { + return contentTypeResource.allowsVariation(); + }, + createIdArray: function(array) { var newArray = []; diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 00458aa6b4..f5be96304f 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,7 +9,7 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource, contentTypeHelper) { $scope.treeApi = {}; @@ -201,18 +201,48 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if (args.key === "showSearchResults") { $scope.showSearchResults = args.value; } + + //load languages if doc types allow variations + if ($scope.currentSection === "content") { + contentTypeHelper.allowsVariation().then(function (b) { + if (b === "true") { + //load languages if there are more than 1 + loadLanguages(); + } else { + $scope.languages = []; + init(); + } + + }); + } })); // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function(e, args) { - languageResource.getAll().then(function(languages) { - $scope.languages = languages; + evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { + languageResource.getAll().then(function (languages) { + contentTypeHelper.allowsVariation().then(function (b) { + + if (b === "true") { + $scope.languages = languages; + } else { + $scope.languages = []; + } + + }); }); })); evts.push(eventsService.on("editors.languages.languageCreated", function(e, args) { languageResource.getAll().then(function(languages) { - $scope.languages = languages; + contentTypeHelper.allowsVariation().then(function (b) { + + if (b === "true") { + $scope.languages = languages; + } else { + $scope.languages = []; + } + + }); }); })); @@ -231,11 +261,12 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //when the application is ready and the user is authorized setup the data - evts.push(eventsService.on("app.ready", function(evt, data) { + evts.push(eventsService.on("app.ready", function (evt, data) { $scope.authenticated = true; - - // load languages - languageResource.getAll().then(function(languages) { + })); + + function loadLanguages() { + languageResource.getAll().then(function (languages) { $scope.languages = languages; if ($scope.languages.length > 1) { @@ -250,7 +281,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar init(); }); - })); + } function init() { //select the current language if set in the query string @@ -281,7 +312,6 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } } - function nodeExpandedHandler(args) { //store the reference to the expanded node path if (args.node) { @@ -289,7 +319,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } } - $scope.selectLanguage = function(language) { + $scope.selectLanguage = function (language) { $location.search("mculture", language.culture); @@ -322,7 +352,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar angularHelper.executeSequentialPromises(promises); }); }); - + }; //this reacts to the options item in the tree diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index 81eb3ab59d..5cf9ca21b3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -31,7 +31,7 @@ position: absolute; border-radius: 0 0 3px 3px; max-height: 200px; - overflow: scroll; + overflow: auto; } .umb-language-picker__dropdown a { @@ -45,4 +45,4 @@ .umb-language-picker__dropdown a:focus { background: @gray-10; text-decoration: none; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index b8df84ad25..f5b890bd8c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -235,7 +235,7 @@ What languages would you like to publish? No languages available to be published. Published Languages. - + Ready to Publish? Create a new Content Template from '%0%' diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 5ffe68afaa..04065e82ce 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -14,6 +14,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Logging; using Umbraco.Web.Composing; +using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Editors { @@ -34,6 +35,12 @@ namespace Umbraco.Web.Editors { return Services.ContentTypeService.Count(); } + [HttpGet] + public bool AllowsVariation() + { + var contentTypes = Services.ContentTypeService.GetAll(); + return contentTypes.Any(contentType => contentType.Variations.HasFlag(ContentVariation.CultureNeutral)); + } public DocumentTypeDisplay GetById(int id) { @@ -169,17 +176,17 @@ namespace Umbraco.Web.Editors } public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) - { - //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. - //If the doc type does not allow content variations, we need to update all of it's property types to not allow this either - //else we may end up with ysods. I'm unsure if the service level handles this but we'll make sure it is updated here - if (!contentTypeSave.AllowCultureVariant) - { - foreach(var prop in contentTypeSave.Groups.SelectMany(x => x.Properties)) - { - prop.AllowCultureVariant = false; - } - } + { + //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. + //If the doc type does not allow content variations, we need to update all of it's property types to not allow this either + //else we may end up with ysods. I'm unsure if the service level handles this but we'll make sure it is updated here + if (!contentTypeSave.AllowCultureVariant) + { + foreach(var prop in contentTypeSave.Groups.SelectMany(x => x.Properties)) + { + prop.AllowCultureVariant = false; + } + } var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index 10fec19b38..aec5c375d9 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -46,12 +46,17 @@ namespace Umbraco.Web.Models.Mapping editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; } - var culture = context.GetCulture(); + var culture = context.GetCulture(); if (culture == null && 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}"); + } + if (property.PropertyType.Variations == ContentVariation.InvariantNeutral) + { + culture = null; + } var result = new TDestination diff --git a/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs b/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs index de7756266f..4039a0e3a2 100644 --- a/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/EmailAddressConfiguration.cs @@ -7,7 +7,7 @@ namespace Umbraco.Web.PropertyEditors /// public class EmailAddressConfiguration { - [ConfigurationField("Required?", "boolean")] + [ConfigurationField("IsRequired", "Required?", "boolean")] public bool IsRequired { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index 3d6d435179..16a3fc92ad 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -147,8 +147,10 @@ namespace Umbraco.Web.Routing // else we have a uri, // try to match that uri, else filter - // pick domains for cultures - var cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture); + // if a culture is specified, then try to get domains for that culture + // (else cultureDomains will be null) + // do NOT specify a default culture, else it would pick those domains + var cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); IReadOnlyCollection considerForBaseDomains = domainsAndUris; if (cultureDomains != null) { diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs index ce95cf4ee0..5553ad00b2 100644 --- a/src/Umbraco.Web/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -1,108 +1,108 @@ -using System; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using umbraco; -using umbraco.cms.businesslogic.packager; -using Umbraco.Core.Services; -using Umbraco.Web._Legacy.Actions; -using Constants = Umbraco.Core.Constants; +//using System; +//using System.Linq; +//using System.Net.Http.Formatting; +//using Umbraco.Web.Models.Trees; +//using Umbraco.Web.Mvc; +//using Umbraco.Web.WebApi.Filters; +//using umbraco; +//using umbraco.cms.businesslogic.packager; +//using Umbraco.Core.Services; +//using Umbraco.Web._Legacy.Actions; +//using Constants = Umbraco.Core.Constants; -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Packages)] - [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class PackagesTreeController : TreeController - { - /// - /// Helper method to create a root model for a tree - /// - /// - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var root = base.CreateRootNode(queryStrings); +//namespace Umbraco.Web.Trees +//{ +// [UmbracoTreeAuthorize(Constants.Trees.Packages)] +// [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] +// [PluginController("UmbracoTrees")] +// [CoreTree] +// public class PackagesTreeController : TreeController +// { +// /// +// /// Helper method to create a root model for a tree +// /// +// /// +// protected override TreeNode CreateRootNode(FormDataCollection queryStrings) +// { +// var root = base.CreateRootNode(queryStrings); - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview"); - root.Icon = "icon-box"; +// //this will load in a custom UI instead of the dashboard for the root node +// root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview"); +// root.Icon = "icon-box"; - return root; - } +// return root; +// } - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); +// protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) +// { +// var nodes = new TreeNodeCollection(); - var createdPackages = CreatedPackage.GetAllCreatedPackages(); +// var createdPackages = CreatedPackage.GetAllCreatedPackages(); - if (id == "created") - { - nodes.AddRange( - createdPackages - .OrderBy(entity => entity.Data.Name) - .Select(dt => - { - var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, - string.Format("/{0}/framed/{1}", - queryStrings.GetValue("application"), - Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); - return node; - })); - } - else - { - //must be root - var node = CreateTreeNode( - "created", - id, - queryStrings, - Services.TextService.Localize("treeHeaders/createdPackages"), - "icon-folder", - createdPackages.Count > 0, - string.Empty); +// if (id == "created") +// { +// nodes.AddRange( +// createdPackages +// .OrderBy(entity => entity.Data.Name) +// .Select(dt => +// { +// var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, +// string.Format("/{0}/framed/{1}", +// queryStrings.GetValue("application"), +// Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); +// return node; +// })); +// } +// else +// { +// //must be root +// var node = CreateTreeNode( +// "created", +// id, +// queryStrings, +// Services.TextService.Localize("treeHeaders/createdPackages"), +// "icon-folder", +// createdPackages.Count > 0, +// string.Empty); - //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. - node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; +// //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. +// node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; - nodes.Add(node); - } +// nodes.Add(node); +// } - return nodes; - } +// return nodes; +// } - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); +// protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) +// { +// var menu = new MenuItemCollection(); - // Root actions - if (id == "-1") - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) - .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); - } - else if (id == "created") - { - menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) - .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); +// // Root actions +// if (id == "-1") +// { +// menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) +// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); +// } +// else if (id == "created") +// { +// menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) +// .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); - menu.Items.Add( - Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); - } - else - { - //it's a package node - menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); - } +// menu.Items.Add( +// Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); +// } +// else +// { +// //it's a package node +// menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)); +// } - return menu; - } - } -} +// return menu; +// } +// } +//}