Skip to content

Commit

Permalink
Merge branch 'v8/dev' into v8/contrib
Browse files Browse the repository at this point in the history
  • Loading branch information
nul800sebastiaan committed Oct 19, 2021
2 parents 57445e8 + 5cc70d2 commit 4dc1ee6
Show file tree
Hide file tree
Showing 16 changed files with 302 additions and 171 deletions.
35 changes: 23 additions & 12 deletions src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Runtime.Serialization;
using System.Text;

namespace Umbraco.Core.Exceptions
{
Expand Down Expand Up @@ -85,18 +86,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
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ protected override void PersistNewItem(IContent entity)
protected override void PersistUpdatedItem(IContent entity)
{
var isEntityDirty = entity.IsDirty();
var editedSnapshot = entity.Edited;

// check if we need to make any database changes at all
if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished)
Expand Down Expand Up @@ -621,6 +622,19 @@ protected override void PersistUpdatedItem(IContent entity)
if (!publishing && entity.PublishName != entity.Name)
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())
{
// bump dates to align cultures to version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,22 @@ 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) continue;
var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
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();

if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0) continue;
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));
}

throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
{
throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
}
}

Expand Down
63 changes: 61 additions & 2 deletions src/Umbraco.Tests/Services/ContentServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,65 @@ public void Cannot_Publish_Content_Awaiting_Release()
Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, published.Result);
}

// V9 - Tests.Integration
[Test]
public void Failed_Publish_Should_Not_Update_Edited_State_When_Edited_True()
{
const int rootNodeId = NodeDto.NodeIdSeed + 2;

// Arrange
var contentService = ServiceContext.ContentService;
var content = contentService.GetById(rootNodeId);
contentService.SaveAndPublish(content);

content.Properties[0].SetValue("Foo", culture: string.Empty);
content.ContentSchedule.Add(DateTime.Now.AddHours(2), null);
contentService.Save(content);

// Act
var result = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId);

// Assert
Assert.Multiple(() =>
{
Assert.IsFalse(result.Success);
Assert.IsTrue(result.Content.Published);
Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, result.Result);

// We changed property data
Assert.IsTrue(result.Content.Edited, "result.Content.Edited");
});
}

// V9 - Tests.Integration
[Test]
public void Failed_Publish_Should_Not_Update_Edited_State_When_Edited_False()
{
const int rootNodeId = NodeDto.NodeIdSeed + 2;

// Arrange
var contentService = ServiceContext.ContentService;
var content = contentService.GetById(rootNodeId);
contentService.SaveAndPublish(content);

content.ContentSchedule.Add(DateTime.Now.AddHours(2), null);
contentService.Save(content);

// Act
var result = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId);

// Assert
Assert.Multiple(() =>
{
Assert.IsFalse(result.Success);
Assert.IsTrue(result.Content.Published);
Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, result.Result);

// We didn't change any property data
Assert.IsFalse(result.Content.Edited, "result.Content.Edited");
});
}

[Test]
public void Cannot_Publish_Culture_Awaiting_Release()
{
Expand Down Expand Up @@ -2176,7 +2235,7 @@ public void Can_Rollback_Version_On_Content()
contentService.Save(rollback2);

Assert.IsTrue(rollback2.Published);
Assert.IsFalse(rollback2.Edited); // all changes cleared!
Assert.IsTrue(rollback2.Edited); // Still edited, change of behaviour

Assert.AreEqual("Jane Doe", rollback2.GetValue<string>("author"));
Assert.AreEqual("Text Page 2 ReReUpdated", rollback2.Name);
Expand All @@ -2195,7 +2254,7 @@ public void Can_Rollback_Version_On_Content()
content.CopyFrom(rollto);
content.Name = rollto.PublishName; // must do it explicitely AND must pick the publish one!
contentService.Save(content);
Assert.IsFalse(content.Edited);
Assert.IsTrue(content.Edited); // Still edited, change of behaviour
Assert.AreEqual("Text Page 2 ReReUpdated", content.Name);
Assert.AreEqual("Jane Doe", content.GetValue("author"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ angular.module('umbraco.mocks').
return [200, nodes, null];
}

function returnUrlsbyUdis(status, data, headers) {

if (!mocksUtils.checkAuth()) {
return [401, null, null];
}

return [200, {}, null];
}

function returnEntitybyIdsPost(method, url, data, headers) {

if (!mocksUtils.checkAuth()) {
Expand Down Expand Up @@ -73,6 +82,10 @@ angular.module('umbraco.mocks').
.whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds'))
.respond(returnEntitybyIdsPost);

$httpBackend
.whenPOST(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetUrlsByUdis'))
.respond(returnUrlsbyUdis);

$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors'))
.respond(returnEntitybyIds);
Expand Down
15 changes: 15 additions & 0 deletions src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,21 @@ function entityResource($q, $http, umbRequestHelper) {
'Failed to retrieve url for id:' + id);
},

getUrlsByUdis: function(udis, culture) {
var query = "culture=" + (culture || "");

return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetUrlsByUdis",
query),
{
udis: udis
}),
'Failed to retrieve url map for udis ' + udis);
},

getUrlByUdi: function (udi, culture) {

if (!udi) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje
group.convertingToTab = true;

group.type = this.TYPE_TAB;

const newAlias = this.generateLocalAlias(group.name);
// when checking for alias uniqueness we need to exclude the current group or the alias would get a + 1
const otherGroups = [...groups].filter(groupCopy => !groupCopy.convertingToTab);
Expand Down Expand Up @@ -439,6 +439,51 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje

array.push(placeholder);

},

rebindSavedContentType: function (contentType, savedContentType) {
// The saved content type might have updated values (eg. new IDs/keys), so make sure the view model is updated
contentType.ModelState = savedContentType.ModelState;
contentType.id = savedContentType.id;

// Prevent rebinding if there was an error: https://github.com/umbraco/Umbraco-CMS/pull/11257
if (savedContentType.ModelState) {
return;
}

contentType.groups.forEach(function (group) {
if (!group.alias) return;

var k = 0;
while (k < savedContentType.groups.length && savedContentType.groups[k].alias != group.alias)
k++;

if (k == savedContentType.groups.length) {
group.id = 0;
return;
}

var savedGroup = savedContentType.groups[k];
group.id = savedGroup.id;
group.key = savedGroup.key;
group.contentTypeId = savedGroup.contentTypeId;

group.properties.forEach(function (property) {
if (property.id || !property.alias) return;

k = 0;
while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
k++;

if (k == savedGroup.properties.length) {
property.id = 0;
return;
}

var savedProperty = savedGroup.properties[k];
property.id = savedProperty.id;
});
});
}

};
Expand Down
Loading

0 comments on commit 4dc1ee6

Please sign in to comment.