Skip to content

Commit 56595a7

Browse files
bergmaniabjarnefcstrijkertsorengnathanwoulfe
authored
Merge v8/dev 20-10-2021 (#11426)
* Adjust icon in umb-checkbox and ensure icon is centered * Missing nl translation for blockEditor_addBlock * Implement icon parameter for doctype editor (#11008) * fix: implement icon parameter for doctype editor issue #10108 * fix: move color from icon to class attribute * fix: removed defined colors, defaulting to the standard dark grey (ie "no color picked" in icon picker) * cleaned up unused dependencies, double quotes to single, removed unused 'color' param from the create methods, and use shorthand object creation in createDocType (if the key has the same name as the variable passed as a prop, we only need to pass the key name) * fix comment Co-authored-by: Nathan Woulfe <[email protected]> * Align sortable handle vertically in multivalues prevalue editor * 10341: Use different picker for content types (#10896) * 10341: Use different picker for content types * use es6 where possible (inc removing underscore for teeny tiny performance improvement) Co-authored-by: Nathan Woulfe <[email protected]> * Falling back to contentTypeName when Block List label is empty (#10963) * Falling back to contentTypeName when Block List label is empty * Adding $contentTypeName variable for Block List labels * Fix incorrect attribute * Grid: Add button styling fix (#10978) * Add missing focus styling * Ensure add button is perfectly rounded and remove unused / uneeded CSS. * Remove redundant border-color property * Revert removal of unused css Co-authored-by: BatJan <[email protected]> Co-authored-by: Jan Skovgaard Olsen <[email protected]> * Create content template localization (#10945) * Don't use self-closing element for custom HTML elements * Use button element for close/cancel in copy dialog * Update localization of "createBlueprintFrom" Co-authored-by: Nathan Woulfe <[email protected]> * Cleanup examine search results, and adds ability to toggle fields (#9141) * Cleanup examine search results, and adds ability to toggle fields * update table to use joinarray filter with one-time binding to avoid recalculating filter values, updated filter to not explode when array arg is null * fix failing tests - improve filter to not fail on non-array params, update tests accordingly Co-authored-by: Nathan Woulfe <[email protected]> * Add EntityController GetUrlsByUdis Enables loading multiple URLs in a single request for Media & Documents * Update content picker to use GetUrlsByUdis * Allows replacing MainDom with alternate DB There are some cases where there is a complex hosting strategy and folks want a readonly database and are hosting on Azure. In that case, it is not entirely possible to have a readonly Umbraco database because SqlMainDom is required and part of that requirement is to have read/write access to the umbraco key value table. This PR allows for the default MainDom to be replaced and to allow for an SqlMainDomLock to use an alternate connection string so that a separate read/write database can be used. * Remove inherited property group id/key when local properties are added (#11231) * Remove inherited property group id/key when local properties are added * Rebind saved content type values * Remove inherited from save group * Rename parameter for clarity * Removes annoying wait text, which causes layout jank * v8: Backoffice Welsh language translation updates (#11240) * Updated the Welsh language file to include newly added keys (based on the en us language file) * Updated the searchInputDescription key * Updated the endTitle key * Use medium sized overlay * Use umb-icon component for icons in content type groups and tabs * fixes wrong reference to enterSubmitFolder method in ng-keydown * 11251: Don't add default dashboard to url * Fix preview of SVG when height and width not are set * If caching a published document, make sure you use the published Name… (#11313) * If caching a published document, make sure you use the published Name. Closes #11074. * Fix case of new node Co-authored-by: Moore, Douglas S <[email protected]> * Added missing Italian translations (#11197) * Resolve incorrect ContentSavedState for failed publish Closes #11290 (for v8) * add modelValue validation for server to correctly update validation errors * 11048: Bugfix for groups and properties that get replaced (#11257) (cherry picked from commit 1605dc1) * Icon fallback to `icon-document` for existing document types (#11283) * Align create buttons styling (#11352) * Added button for cancelling dictionary create action * Use hideMenu * Align dictionary create with the other creates * Align import documenttype * Align for data type folder create * Align document type create buttons * Forgot small ng-show * Align create media folder buttons * Align create macro buttons * Align create relation buttons * Align create partial view macro folder buttons * Align partial view folder create buttons * Align create scripts folder buttons * Align create scripts folder buttons * Use primary instead of success * V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291) * Prevented duplicate member group names * Added English lang * Updated 'Exist' typo * add labels in FR and NL * Adding property group aliases to ex.message * Adding invalid prop group aliases as ModelState errors, so we don't introduce breaking changes * Pointing the actual reason for invalidating composition * Validate all content type dependencies and throw a single InvalidCompositionException * Rename based on review comments * Update composition validation error messages * Update InvalidCompositionException message * Allow switching property editor from numeric to slider (#11287) * Make it possible to change from numeric/decimal property editor to slider without breaking editor * Formatting * Enables friendly pasting in multipletextbox * UI API docs: Added reset rules for .close class * UI API docs: Fixed incorrect method name * 11331: Check property on instance if id is not set yet * Fixed cypress tests Co-authored-by: Bjarne Fyrstenborg <[email protected]> Co-authored-by: Corné Strijkert <[email protected]> Co-authored-by: Søren Gregersen <[email protected]> Co-authored-by: Nathan Woulfe <[email protected]> Co-authored-by: patrickdemooij9 <[email protected]> Co-authored-by: Callum Whyte <[email protected]> Co-authored-by: Jan Skovgaard <[email protected]> Co-authored-by: BatJan <[email protected]> Co-authored-by: Jan Skovgaard Olsen <[email protected]> Co-authored-by: Søren Kottal <[email protected]> Co-authored-by: Paul Johnson <[email protected]> Co-authored-by: Shannon <[email protected]> Co-authored-by: Sebastiaan Janssen <[email protected]> Co-authored-by: Ronald Barendse <[email protected]> Co-authored-by: Owain Jones <[email protected]> Co-authored-by: Mole <[email protected]> Co-authored-by: Doug Moore <[email protected]> Co-authored-by: Moore, Douglas S <[email protected]> Co-authored-by: Martino Gabrielli <[email protected]> Co-authored-by: Nikolaj Geisle <[email protected]> Co-authored-by: Mads Rasmussen <[email protected]> Co-authored-by: Jamie Townsend <[email protected]> Co-authored-by: Elitsa Marinovska <[email protected]> Co-authored-by: Anders Bjerner <[email protected]>
1 parent 9494591 commit 56595a7

File tree

79 files changed

+4158
-21853
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+4158
-21853
lines changed

src/Umbraco.Core/Exceptions/InvalidCompositionException.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Runtime.Serialization;
33
using Umbraco.Extensions;
4+
using System.Text;
45

56
namespace Umbraco.Cms.Core.Exceptions
67
{
@@ -86,18 +87,28 @@ public InvalidCompositionException(string contentTypeAlias, string addedComposit
8687

8788
private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases)
8889
{
89-
// TODO Add property group aliases to message
90-
return addedCompositionAlias.IsNullOrWhiteSpace()
91-
? string.Format(
92-
"ContentType with alias '{0}' has an invalid composition " +
93-
"and there was a conflict on the following PropertyTypes: '{1}'. " +
94-
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
95-
contentTypeAlias, string.Join(", ", propertyTypeAliases))
96-
: string.Format(
97-
"ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " +
98-
"but there was a conflict on the following PropertyTypes: '{2}'. " +
99-
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
100-
addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases));
90+
var sb = new StringBuilder();
91+
92+
if (addedCompositionAlias.IsNullOrWhiteSpace())
93+
{
94+
sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias);
95+
}
96+
else
97+
{
98+
sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias);
99+
}
100+
101+
if (propertyTypeAliases.Length > 0)
102+
{
103+
sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases));
104+
}
105+
106+
if (propertyGroupAliases.Length > 0)
107+
{
108+
sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases));
109+
}
110+
111+
return sb.ToString();
101112
}
102113

103114
/// <summary>

src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ private static string GetUrlSegmentSource(IContentBase content, string culture)
3232
if (content.HasProperty(Constants.Conventions.Content.UrlName))
3333
source = (content.GetValue<string>(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim();
3434
if (string.IsNullOrWhiteSpace(source))
35-
source = content.GetCultureName(culture);
35+
{
36+
// If the name of a node has been updated, but it has not been published, the url should use the published name, not the current node name
37+
// If this node has never been published (GetPublishName is null), use the unpublished name
38+
source = (content is IContent document) && document.Edited && document.GetPublishName(culture) != null
39+
? document.GetPublishName(culture)
40+
: content.GetCultureName(culture);
41+
}
3642
return source;
3743
}
3844
}

src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,19 @@ protected override void PersistUpdatedItem(IContent entity)
673673
edited = true;
674674
}
675675

676+
// To establish the new value of "edited" we compare all properties publishedValue to editedValue and look
677+
// for differences.
678+
//
679+
// If we SaveAndPublish but the publish fails (e.g. already scheduled for release)
680+
// we have lost the publishedValue on IContent (in memory vs database) so we cannot correctly make that comparison.
681+
//
682+
// This is a slight change to behaviour, historically a publish, followed by change & save, followed by undo change & save
683+
// would change edited back to false.
684+
if (!publishing && editedSnapshot)
685+
{
686+
edited = true;
687+
}
688+
676689
if (entity.ContentType.VariesByCulture())
677690
{
678691
// names also impact 'edited'

src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private void RecursePropertyValues(IEnumerable<BlockItemData> blockData, Func<Gu
122122
{
123123
json = JToken.Parse(asString);
124124
}
125-
catch (Exception e)
125+
catch (Exception)
126126
{
127127
// See issue https://github.com/umbraco/Umbraco-CMS/issues/10879
128128
// We are detecting JSON data by seeing if a string is surrounded by [] or {}

src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ protected void ValidateLocked(TItem compositionContentType)
134134
stack.Push(c);
135135
}
136136

137+
var duplicatePropertyTypeAliases = new List<string>();
138+
var invalidPropertyGroupAliases = new List<string>();
139+
137140
foreach (var dependency in dependencies)
138141
{
139142
if (dependency.Id == compositionContentType.Id)
@@ -143,13 +146,14 @@ protected void ValidateLocked(TItem compositionContentType)
143146
if (contentTypeDependency == null)
144147
continue;
145148

146-
var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray();
147-
var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray();
149+
duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
150+
invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
151+
}
148152

149-
if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0)
150-
continue;
153+
if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
151154

152-
throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
155+
{
156+
throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
153157
}
154158
}
155159

src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,12 @@ private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid<TConte
475475
var validateAttempt = service.ValidateComposition(composition);
476476
if (validateAttempt == false)
477477
{
478-
//if it's not successful then we need to return some model state for the property aliases that
479-
// are duplicated
480-
var invalidPropertyAliases = validateAttempt.Result.Distinct();
481-
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidPropertyAliases);
478+
// if it's not successful then we need to return some model state for the property type and property group
479+
// aliases that are duplicated
480+
var duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
481+
var invalidPropertyGroupAliases = (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? Array.Empty<string>();
482+
483+
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
482484

483485
var display = UmbracoMapper.Map<TContentTypeDisplay>(composition);
484486
//map the 'save' data on top
@@ -505,22 +507,32 @@ public IContentTypeBaseService<T> GetContentTypeService<T>()
505507
/// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
506508
/// </summary>
507509
/// <param name="contentTypeSave"></param>
508-
/// <param name="invalidPropertyAliases"></param>
510+
/// <param name="duplicatePropertyTypeAliases"></param>
511+
/// <param name="invalidPropertyGroupAliases"></param>
509512
/// <returns></returns>
510-
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> invalidPropertyAliases)
513+
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> duplicatePropertyTypeAliases, IEnumerable<string> invalidPropertyGroupAliases)
511514
where TContentTypeSave : ContentTypeSave<TPropertyType>
512515
where TPropertyType : PropertyTypeBasic
513516
{
514-
foreach (var propertyAlias in invalidPropertyAliases)
517+
foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
515518
{
516-
// Find the property relating to these
517-
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
519+
// Find the property type relating to these
520+
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyTypeAlias);
518521
var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
519522
var propertyIndex = group.Properties.IndexOf(property);
520523
var groupIndex = contentTypeSave.Groups.IndexOf(group);
521524

522525
var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias";
523-
ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
526+
ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions");
527+
}
528+
529+
foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
530+
{
531+
// Find the property group relating to these
532+
var group = contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
533+
var groupIndex = contentTypeSave.Groups.IndexOf(group);
534+
var key = $"Groups[{groupIndex}].Name";
535+
ModelState.AddModelError(key, "Different group types aren't allowed between compositions");
524536
}
525537
}
526538

@@ -552,7 +564,7 @@ private TContentTypeDisplay CreateInvalidCompositionResponseException<TContentTy
552564
}
553565
if (invalidCompositionException != null)
554566
{
555-
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
567+
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
556568
return CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
557569
}
558570
return null;

src/Umbraco.Web.BackOffice/Controllers/EntityController.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ public EntityController(
120120
_appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
121121
}
122122

123+
124+
123125
/// <summary>
124126
/// Returns an Umbraco alias given a string
125127
/// </summary>
@@ -288,6 +290,50 @@ public IActionResult GetUrl(Udi id, string culture = "*")
288290
return GetUrl(intId.Result, entityType, culture);
289291
}
290292

293+
/// <summary>
294+
/// Get entity URLs by UDIs
295+
/// </summary>
296+
/// <param name="udis">
297+
/// A list of UDIs to lookup items by
298+
/// </param>
299+
/// <param name="culture">The culture to fetch the URL for</param>
300+
/// <returns>Dictionary mapping Udi -> Url</returns>
301+
/// <remarks>
302+
/// We allow for POST because there could be quite a lot of Ids.
303+
/// </remarks>
304+
[HttpGet]
305+
[HttpPost]
306+
public IDictionary<Udi, string> GetUrlsByUdis([FromJsonPath] Udi[] udis, string culture = null)
307+
{
308+
if (udis == null || udis.Length == 0)
309+
{
310+
return new Dictionary<Udi, string>();
311+
}
312+
313+
// TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9)
314+
string MediaOrDocumentUrl(Udi udi)
315+
{
316+
if (udi is not GuidUdi guidUdi)
317+
{
318+
return null;
319+
}
320+
321+
return guidUdi.EntityType switch
322+
{
323+
Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid, culture: culture ?? ClientCulture()),
324+
// NOTE: If culture is passed here we get an empty string rather than a media item URL WAT
325+
Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(guidUdi.Guid, culture: null),
326+
_ => null
327+
};
328+
}
329+
330+
return udis
331+
.Select(udi => new {
332+
Udi = udi,
333+
Url = MediaOrDocumentUrl(udi)
334+
}).ToDictionary(x => x.Udi, x => x.Url);
335+
}
336+
291337
/// <summary>
292338
/// Gets the URL of an entity
293339
/// </summary>

src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ public MemberGroupDisplay GetEmpty()
120120
return _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(item);
121121
}
122122

123+
public bool IsMemberGroupNameUnique(int id, string oldName, string newName)
124+
{
125+
if (newName == oldName)
126+
return true; // name hasn't changed
127+
128+
var memberGroup = _memberGroupService.GetByName(newName);
129+
if (memberGroup == null)
130+
return true; // no member group found
131+
132+
return memberGroup.Id == id;
133+
}
134+
123135
public ActionResult<MemberGroupDisplay> PostSave(MemberGroupSave saveModel)
124136
{
125137

@@ -130,16 +142,28 @@ public ActionResult<MemberGroupDisplay> PostSave(MemberGroupSave saveModel)
130142
return NotFound();
131143
}
132144

133-
memberGroup.Name = saveModel.Name;
134-
_memberGroupService.Save(memberGroup);
145+
if (IsMemberGroupNameUnique(memberGroup.Id, memberGroup.Name, saveModel.Name))
146+
{
147+
memberGroup.Name = saveModel.Name;
148+
_memberGroupService.Save(memberGroup);
135149

136-
MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
150+
MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
137151

138-
display.AddSuccessNotification(
139-
_localizedTextService.Localize("speechBubbles","memberGroupSavedHeader"),
140-
string.Empty);
152+
display.AddSuccessNotification(
153+
_localizedTextService.Localize("speechBubbles", "memberGroupSavedHeader"),
154+
string.Empty);
141155

142-
return display;
156+
return display;
157+
}
158+
else
159+
{
160+
var display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
161+
display.AddErrorNotification(
162+
_localizedTextService.Localize("speechBubbles", "memberGroupNameDuplicate"),
163+
string.Empty);
164+
165+
return display;
166+
}
143167
}
144168
}
145169
}

0 commit comments

Comments
 (0)