diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 953ef5ccce..00e9c1e30c 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models { @@ -173,7 +174,8 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges(o => { - _alias = value.ToSafeAlias(); + //_alias = value.ToSafeAlias(); + _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); return _alias; }, _alias, AliasSelector); } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index ddc06a5d47..d38a516911 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models { @@ -96,7 +97,7 @@ namespace Umbraco.Core.Models { SetPropertyValueAndDetectChanges(o => { - _alias = value; + _alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase); return _alias; }, _alias, AliasSelector); } diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs index 4c53be4cb8..ea9603ec6f 100644 --- a/src/Umbraco.Core/Strings/CleanStringType.cs +++ b/src/Umbraco.Core/Strings/CleanStringType.cs @@ -57,8 +57,9 @@ namespace Umbraco.Core.Strings /// /// Umbraco "safe alias" case. /// - /// This is for backward compatibility. Casing is unchanged within terms, - /// and is pascal otherwise. + /// Uppercases the first char of each term except for the first + /// char of the string, everything else including the first char of the + /// string is unchanged. UmbracoCase = 0x20, diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 526706aa3a..84233e59bf 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -287,7 +287,7 @@ namespace Umbraco.Core.Strings var UMBRACO_FORCE_SAFE_ALIAS = {0}; var UMBRACO_FORCE_SAFE_ALIAS_URL = '{1}'; var UMBRACO_FORCE_SAFE_ALIAS_TIMEOUT = 666; -var UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS = {{ }}; +var UMBRACO_FORCE_SAFE_ALIAS_TMKEY = 'safe-alias-tmout'; function getSafeAliasFromServer(value, callback) {{ $.getJSON(UMBRACO_FORCE_SAFE_ALIAS_URL + 'ToSafeAlias?value=' + encodeURIComponent(value), function(json) {{ @@ -295,28 +295,30 @@ function getSafeAliasFromServer(value, callback) {{ }}); }} -function getSafeAlias(id, value, immediate, callback) {{ +function getSafeAlias(input, value, immediate, callback) {{ if (!UMBRACO_FORCE_SAFE_ALIAS) {{ callback(value); return; }} - if (UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id]) clearTimeout(UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id]); - UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id] = setTimeout(function() {{ - UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id] = null; + var timeout = input.data(UMBRACO_FORCE_SAFE_ALIAS_TMKEY); + if (timeout) clearTimeout(timeout); + input.data(UMBRACO_FORCE_SAFE_ALIAS_TMKEY, setTimeout(function() {{ + input.removeData(UMBRACO_FORCE_SAFE_ALIAS_TMKEY); getSafeAliasFromServer(value, function(alias) {{ callback(alias); }}); - }}, UMBRACO_FORCE_SAFE_ALIAS_TIMEOUT); + }}, UMBRACO_FORCE_SAFE_ALIAS_TIMEOUT)); }} -function validateSafeAlias(id, value, immediate, callback) {{ +function validateSafeAlias(input, value, immediate, callback) {{ if (!UMBRACO_FORCE_SAFE_ALIAS) {{ callback(true); return; }} - if (UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id]) clearTimeout(UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id]); - UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id] = setTimeout(function() {{ - UMBRACO_FORCE_SAFE_ALIAS_TIMEOUTS[id] = null; + var timeout = input.data(UMBRACO_FORCE_SAFE_ALIAS_TMKEY); + if (timeout) clearTimeout(timeout); + input.data(UMBRACO_FORCE_SAFE_ALIAS_TMKEY, setTimeout(function() {{ + input.removeData(UMBRACO_FORCE_SAFE_ALIAS_TMKEY); getSafeAliasFromServer(value, function(alias) {{ callback(value.toLowerCase() == alias.toLowerCase()); }}); - }}, UMBRACO_FORCE_SAFE_ALIAS_TIMEOUT); + }}, UMBRACO_FORCE_SAFE_ALIAS_TIMEOUT)); }} "; diff --git a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs b/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs index 1c780703f4..901c9ee299 100644 --- a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs @@ -74,11 +74,11 @@ function safeAlias(alias) {{ return alias; }} -function getSafeAlias(id, value, immediate, callback) {{ +function getSafeAlias(input, value, immediate, callback) {{ callback(safeAlias(value)); }} -function validateSafeAlias(id, value, immediate, callback) {{ +function validateSafeAlias(input, value, immediate, callback) {{ callback(value == safeAlias(value)); }} diff --git a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx index 3a9bd18b96..6c8d26b4d6 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx @@ -114,7 +114,7 @@ checkAlias('.prop-alias'); - duplicatePropertyNameAsSafeAlias('ul.addNewProperty .prop-name', 'ul.addNewProperty .prop-alias'); + duplicatePropertyNameAsSafeAlias('ul.addNewProperty'); jQuery(".picker-icons").click(function(){ var that = this; diff --git a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js b/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js index 5242c9d5aa..77bfa813d9 100644 --- a/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js +++ b/src/Umbraco.Web.UI/umbraco_client/GenericProperty/genericProperty.js @@ -12,31 +12,35 @@ function expandCollapse(theId) { document.getElementById("desc" + theId).style.display = 'block'; } } -function duplicatePropertyNameAsSafeAlias(nameId, aliasId) { - var input = $(aliasId); - - $(nameId).keyup(function(event) { - var value = $(this).val(); - getSafeAlias(aliasId, value, false, function (alias) { - input.val(alias); +function duplicatePropertyNameAsSafeAlias(propertySelector) { + $(propertySelector).each(function() { + var prop = $(this); + var inputName = prop.find('.prop-name'); + var inputAlias = prop.find('.prop-alias'); + inputName.on('input', function (event) { + getSafeAlias(inputAlias, inputName.val(), false, function (alias) { + inputAlias.val(alias); + }); + }).on('blur', function (event) { + $(this).off('input'); }); }); } -function checkAlias(aliasId) { - $(aliasId).keyup(function (event) { +function checkAlias(aliasSelector) { + $(aliasSelector).keyup(function (event) { var input = $(this); var value = input.val(); - validateSafeAlias(aliasId, value, false, function (isSafe) { - input.toggleClass('aliasValidationError', !isSafe); + validateSafeAlias(input, value, false, function (isSafe) { + input.toggleClass('highlight-error', !isSafe); }); }).blur(function(event) { var input = $(this); var value = input.val(); - getSafeAlias(aliasId, value, true, function (alias) { + getSafeAlias(input, value, true, function (alias) { if (value.toLowerCase() != alias.toLowerCase()) input.val(alias); - input.removeClass('aliasValidationError'); + input.removeClass('highlight-error'); }); }); } diff --git a/src/Umbraco.Web/WebServices/CoreStringsController.cs b/src/Umbraco.Web/WebServices/CoreStringsController.cs index 78eb449273..6da704a520 100644 --- a/src/Umbraco.Web/WebServices/CoreStringsController.cs +++ b/src/Umbraco.Web/WebServices/CoreStringsController.cs @@ -20,9 +20,11 @@ namespace Umbraco.Web.WebServices [HttpGet] public JsonResult ToSafeAlias(string value, bool camelCase = true) { + // always return a proper camel-cased alias + // when checking... javascript does a case-unsensitive comparison return value == null ? Json(new {error = "no value."}, JsonRequestBehavior.AllowGet) - : Json(new { alias = value.ToSafeAlias(camelCase) }, JsonRequestBehavior.AllowGet); + : Json(new { alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.CamelCase) }, JsonRequestBehavior.AllowGet); } [HttpGet] diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 5934b6e7e7..7f58c326b3 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -216,8 +216,8 @@ namespace umbraco if (CacheByPersonalization) { - var currentMember = Member.GetCurrentMember(); - id.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); + int currentMember = Member.CurrentMemberId(); + id.AppendFormat("m{0}-", currentMember); } foreach (var prop in model.Properties) @@ -525,7 +525,7 @@ namespace umbraco if (Model.CacheDuration > 0) { // do not add to cache if there's no member and it should cache by personalization - if (!Model.CacheByMember || (Model.CacheByMember && Member.GetCurrentMember() != null)) + if (!Model.CacheByMember || (Model.CacheByMember && Member.IsLoggedOn())) { if (macroControl != null) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 6f68024c79..7a94afced9 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -293,7 +293,7 @@ namespace umbraco.controls global::Umbraco.Web.UmbracoContext.Current = asyncState.UmbracoContext; _contentType.ContentTypeItem.Name = txtName.Text; - _contentType.ContentTypeItem.Alias = txtAlias.Text; + _contentType.ContentTypeItem.Alias = txtAlias.Text; // raw, contentType.Alias takes care of it _contentType.ContentTypeItem.Icon = tb_icon.Value; _contentType.ContentTypeItem.Description = description.Text; //_contentType.ContentTypeItem.Thumbnail = ddlThumbnails.SelectedValue; @@ -832,7 +832,9 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); GenericProperty gpData = gp.GenricPropertyControl; if (string.IsNullOrEmpty(gpData.Name.Trim()) == false && string.IsNullOrEmpty(gpData.Alias.Trim()) == false) { - var propertyTypeAlias = Casing.SafeAliasWithForcingCheck(gpData.Alias.Trim()); + // when creating a property don't do anything special, propertyType.Alias will take care of it + // don't enforce camel here because the user might have changed what the CoreStringsController returned + var propertyTypeAlias = gpData.Alias; if (contentTypeItem.PropertyTypeExists(propertyTypeAlias) == false) { //Find the DataTypeDefinition that the PropertyType should be based on @@ -889,6 +891,7 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); var propertyType = contentTypeItem.PropertyTypes.First(x => x.Alias == gpw.PropertyType.Alias); if (propertyType == null) continue; var dataTypeDefinition = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(gpw.GenricPropertyControl.Type); + // when saving, respect user's casing, so do nothing here as propertyType takes care of it propertyType.Alias = gpw.GenricPropertyControl.Alias; propertyType.Name = gpw.GenricPropertyControl.Name; propertyType.Description = gpw.GenricPropertyControl.Description; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs index 9c20f3b889..0b54756b74 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs @@ -5,6 +5,7 @@ using System.Web.Security; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Strings; using Umbraco.Web.UI; using umbraco.BusinessLogic; using umbraco.DataLayer; @@ -26,7 +27,9 @@ namespace umbraco ? new ContentType(-1) : new ContentType(ApplicationContext.Current.Services.ContentTypeService.GetContentType(parentId)); contentType.CreatorId = User.Id; - contentType.Alias = Alias.Replace("'", "''"); + // when creating a content type, enforce PascalCase + // preserve separator because contentType.Alias will re-alias it + contentType.Alias = Alias.ToCleanString(CleanStringType.Alias | CleanStringType.PascalCase, ' '); contentType.Name = Alias.Replace("'", "''"); contentType.Icon = ".sprTreeFolder";