Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge v8/dev 20-10-2021 #11426

Merged
merged 53 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c49c536
Adjust icon in umb-checkbox and ensure icon is centered
bjarnef Sep 19, 2021
674edd3
Missing nl translation for blockEditor_addBlock
cstrijkert Sep 20, 2021
9a246a1
Implement icon parameter for doctype editor (#11008)
soreng Sep 21, 2021
46e6f05
Align sortable handle vertically in multivalues prevalue editor
bjarnef Sep 19, 2021
1e1276f
10341: Use different picker for content types (#10896)
patrickdemooij9 Sep 21, 2021
405ed44
Falling back to contentTypeName when Block List label is empty (#10963)
callumbwhyte Sep 21, 2021
6c51210
Fix incorrect attribute
bjarnef Sep 16, 2021
31ecc14
Grid: Add button styling fix (#10978)
BatJan Sep 21, 2021
48a3a05
Create content template localization (#10945)
bjarnef Sep 22, 2021
ffa704c
Cleanup examine search results, and adds ability to toggle fields (#9…
skttl Sep 22, 2021
26382ab
Add EntityController GetUrlsByUdis
Sep 27, 2021
4eb7579
Update content picker to use GetUrlsByUdis
Sep 27, 2021
9f48a9f
Allows replacing MainDom with alternate DB
Shazwazza Sep 10, 2021
7c77ba2
Merge branch 'v8/8.17' into v8/dev
nul800sebastiaan Sep 30, 2021
c3ab0ae
Merge branch 'v8/dev' into v8/contrib
nul800sebastiaan Sep 30, 2021
20b9db8
Remove inherited property group id/key when local properties are adde…
ronaldbarendse Oct 4, 2021
106f6dc
Rename parameter for clarity
Oct 5, 2021
ef725ba
Removes annoying wait text, which causes layout jank
skttl Oct 5, 2021
3fcfb1b
v8: Backoffice Welsh language translation updates (#11240)
OwainJ Oct 5, 2021
7043d1c
Use medium sized overlay
BatJan Sep 27, 2021
c7f342a
Use umb-icon component for icons in content type groups and tabs
bjarnef Sep 1, 2021
828558a
fixes wrong reference to enterSubmitFolder method in ng-keydown
skttl Oct 2, 2021
e36dd86
Merge pull request #11207 from umbraco/v8/bugfix/mntp-performance
nikolajlauridsen Oct 6, 2021
8619cfe
11251: Don't add default dashboard to url
patrickdemooij9 Oct 4, 2021
494bef8
Fix preview of SVG when height and width not are set
bjarnef Oct 5, 2021
f7ea400
If caching a published document, make sure you use the published Name…
lordscarlet Oct 11, 2021
359181c
Added missing Italian translations (#11197)
ZioTino Oct 11, 2021
f6f5723
Resolve incorrect ContentSavedState for failed publish
Oct 12, 2021
4c6e014
Merge pull request #11356 from umbraco/v8/bugfix/fix-incorrect-edited…
Zeegaan Oct 12, 2021
3d53c72
add modelValue validation for server to correctly update validation e…
madsrasmussen Oct 12, 2021
d02440d
11048: Bugfix for groups and properties that get replaced (#11257)
patrickdemooij9 Oct 12, 2021
93720c9
Icon fallback to `icon-document` for existing document types (#11283)
bjarnef Oct 12, 2021
03b2aed
Align create buttons styling (#11352)
patrickdemooij9 Oct 12, 2021
08075e5
V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291)
Oct 13, 2021
3f3262e
Adding property group aliases to ex.message
elit0451 Oct 14, 2021
ccd9013
Adding invalid prop group aliases as ModelState errors, so we don't i…
elit0451 Oct 14, 2021
b8ecc17
Pointing the actual reason for invalidating composition
elit0451 Oct 14, 2021
1b06db8
Validate all content type dependencies and throw a single InvalidComp…
ronaldbarendse Oct 14, 2021
6666f6a
Rename based on review comments
elit0451 Oct 14, 2021
844cd95
Merge branch 'v8/bugfix/AB14159-add-more-warnings-when-invalid-compos…
elit0451 Oct 14, 2021
705a3ed
Update composition validation error messages
ronaldbarendse Oct 14, 2021
db50281
Merge branch 'v8/bugfix/AB14159-add-more-warnings-when-invalid-compos…
ronaldbarendse Oct 14, 2021
b73ab25
Update InvalidCompositionException message
ronaldbarendse Oct 14, 2021
a284d52
Merge pull request #11373 from umbraco/v8/bugfix/AB14159-add-more-war…
ronaldbarendse Oct 14, 2021
6a5e34b
Allow switching property editor from numeric to slider (#11287)
bjarnef Oct 15, 2021
7ec1147
Enables friendly pasting in multipletextbox
skttl Oct 5, 2021
8e86f3e
UI API docs: Added reset rules for .close class
abjerner Oct 18, 2021
eb2f152
UI API docs: Fixed incorrect method name
abjerner Oct 18, 2021
57445e8
11331: Check property on instance if id is not set yet
patrickdemooij9 Oct 8, 2021
5cc70d2
Merge pull request #11360 from umbraco/v8/bugfix/11057-mandatory-imag…
Zeegaan Oct 19, 2021
4dc1ee6
Merge branch 'v8/dev' into v8/contrib
nul800sebastiaan Oct 19, 2021
f30f314
Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge-v8…
bergmania Oct 20, 2021
bf1a1fe
Fixed cypress tests
bergmania Oct 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Extensions;
using System.Text;

namespace Umbraco.Cms.Core.Exceptions
{
Expand Down Expand Up @@ -86,18 +87,28 @@ public InvalidCompositionException(string contentTypeAlias, string addedComposit

private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases)
{
// TODO Add property group aliases to message
return addedCompositionAlias.IsNullOrWhiteSpace()
? string.Format(
"ContentType with alias '{0}' has an invalid composition " +
"and there was a conflict on the following PropertyTypes: '{1}'. " +
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
contentTypeAlias, string.Join(", ", propertyTypeAliases))
: string.Format(
"ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " +
"but there was a conflict on the following PropertyTypes: '{2}'. " +
"PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases));
var sb = new StringBuilder();

if (addedCompositionAlias.IsNullOrWhiteSpace())
{
sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias);
}
else
{
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);
}

if (propertyTypeAliases.Length > 0)
{
sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases));
}

if (propertyGroupAliases.Length > 0)
{
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));
}

return sb.ToString();
}

/// <summary>
Expand Down
8 changes: 7 additions & 1 deletion src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ private static string GetUrlSegmentSource(IContentBase content, string culture)
if (content.HasProperty(Constants.Conventions.Content.UrlName))
source = (content.GetValue<string>(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(source))
source = content.GetCultureName(culture);
{
// 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
// If this node has never been published (GetPublishName is null), use the unpublished name
source = (content is IContent document) && document.Edited && document.GetPublishName(culture) != null
? document.GetPublishName(culture)
: content.GetCultureName(culture);
}
return source;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,19 @@ protected override void PersistUpdatedItem(IContent entity)
edited = true;
}

// To establish the new value of "edited" we compare all properties publishedValue to editedValue and look
// for differences.
//
// If we SaveAndPublish but the publish fails (e.g. already scheduled for release)
// we have lost the publishedValue on IContent (in memory vs database) so we cannot correctly make that comparison.
//
// This is a slight change to behaviour, historically a publish, followed by change & save, followed by undo change & save
// would change edited back to false.
if (!publishing && editedSnapshot)
{
edited = true;
}

if (entity.ContentType.VariesByCulture())
{
// names also impact 'edited'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private void RecursePropertyValues(IEnumerable<BlockItemData> blockData, Func<Gu
{
json = JToken.Parse(asString);
}
catch (Exception e)
catch (Exception)
{
// See issue https://github.com/umbraco/Umbraco-CMS/issues/10879
// We are detecting JSON data by seeing if a string is surrounded by [] or {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ protected void ValidateLocked(TItem compositionContentType)
stack.Push(c);
}

var duplicatePropertyTypeAliases = new List<string>();
var invalidPropertyGroupAliases = new List<string>();

foreach (var dependency in dependencies)
{
if (dependency.Id == compositionContentType.Id)
Expand All @@ -143,13 +146,14 @@ protected void ValidateLocked(TItem compositionContentType)
if (contentTypeDependency == null)
continue;

var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray();
var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray();
duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
}

if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0)
continue;
if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)

throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
{
throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
}
}

Expand Down
34 changes: 23 additions & 11 deletions src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,12 @@ private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid<TConte
var validateAttempt = service.ValidateComposition(composition);
if (validateAttempt == false)
{
//if it's not successful then we need to return some model state for the property aliases that
// are duplicated
var invalidPropertyAliases = validateAttempt.Result.Distinct();
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidPropertyAliases);
// if it's not successful then we need to return some model state for the property type and property group
// aliases that are duplicated
var duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
var invalidPropertyGroupAliases = (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? Array.Empty<string>();

AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);

var display = UmbracoMapper.Map<TContentTypeDisplay>(composition);
//map the 'save' data on top
Expand All @@ -505,22 +507,32 @@ public IContentTypeBaseService<T> GetContentTypeService<T>()
/// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
/// </summary>
/// <param name="contentTypeSave"></param>
/// <param name="invalidPropertyAliases"></param>
/// <param name="duplicatePropertyTypeAliases"></param>
/// <param name="invalidPropertyGroupAliases"></param>
/// <returns></returns>
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> invalidPropertyAliases)
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> duplicatePropertyTypeAliases, IEnumerable<string> invalidPropertyGroupAliases)
where TContentTypeSave : ContentTypeSave<TPropertyType>
where TPropertyType : PropertyTypeBasic
{
foreach (var propertyAlias in invalidPropertyAliases)
foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
{
// Find the property relating to these
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
// Find the property type relating to these
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyTypeAlias);
var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
var propertyIndex = group.Properties.IndexOf(property);
var groupIndex = contentTypeSave.Groups.IndexOf(group);

var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias";
ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions");
}

foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
{
// Find the property group relating to these
var group = contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
var groupIndex = contentTypeSave.Groups.IndexOf(group);
var key = $"Groups[{groupIndex}].Name";
ModelState.AddModelError(key, "Different group types aren't allowed between compositions");
}
}

Expand Down Expand Up @@ -552,7 +564,7 @@ private TContentTypeDisplay CreateInvalidCompositionResponseException<TContentTy
}
if (invalidCompositionException != null)
{
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
return CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
}
return null;
Expand Down
46 changes: 46 additions & 0 deletions src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public EntityController(
_appCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
}



/// <summary>
/// Returns an Umbraco alias given a string
/// </summary>
Expand Down Expand Up @@ -288,6 +290,50 @@ public IActionResult GetUrl(Udi id, string culture = "*")
return GetUrl(intId.Result, entityType, culture);
}

/// <summary>
/// Get entity URLs by UDIs
/// </summary>
/// <param name="udis">
/// A list of UDIs to lookup items by
/// </param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>Dictionary mapping Udi -> Url</returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
public IDictionary<Udi, string> GetUrlsByUdis([FromJsonPath] Udi[] udis, string culture = null)
{
if (udis == null || udis.Length == 0)
{
return new Dictionary<Udi, string>();
}

// TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9)
string MediaOrDocumentUrl(Udi udi)
{
if (udi is not GuidUdi guidUdi)
{
return null;
}

return guidUdi.EntityType switch
{
Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid, culture: culture ?? ClientCulture()),
// NOTE: If culture is passed here we get an empty string rather than a media item URL WAT
Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(guidUdi.Guid, culture: null),
_ => null
};
}

return udis
.Select(udi => new {
Udi = udi,
Url = MediaOrDocumentUrl(udi)
}).ToDictionary(x => x.Udi, x => x.Url);
}

/// <summary>
/// Gets the URL of an entity
/// </summary>
Expand Down
38 changes: 31 additions & 7 deletions src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ public MemberGroupDisplay GetEmpty()
return _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(item);
}

public bool IsMemberGroupNameUnique(int id, string oldName, string newName)
{
if (newName == oldName)
return true; // name hasn't changed

var memberGroup = _memberGroupService.GetByName(newName);
if (memberGroup == null)
return true; // no member group found

return memberGroup.Id == id;
}

public ActionResult<MemberGroupDisplay> PostSave(MemberGroupSave saveModel)
{

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

memberGroup.Name = saveModel.Name;
_memberGroupService.Save(memberGroup);
if (IsMemberGroupNameUnique(memberGroup.Id, memberGroup.Name, saveModel.Name))
{
memberGroup.Name = saveModel.Name;
_memberGroupService.Save(memberGroup);

MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);

display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles","memberGroupSavedHeader"),
string.Empty);
display.AddSuccessNotification(
_localizedTextService.Localize("speechBubbles", "memberGroupSavedHeader"),
string.Empty);

return display;
return display;
}
else
{
var display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
display.AddErrorNotification(
_localizedTextService.Localize("speechBubbles", "memberGroupNameDuplicate"),
string.Empty);

return display;
}
}
}
}
Loading