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

Code changes from V13.4 #16652

Merged
merged 53 commits into from
Jun 25, 2024
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
52c21b0
Updates JSON schema for Umbraco 10 with latest references for Forms a…
AndyButland Mar 20, 2024
6379f2f
Ported over #15928 changes for 13.3 RC (#16023)
kjac Apr 16, 2024
a6a76d1
Make the API content response builder extendable (#16056)
kjac Apr 16, 2024
a325ba3
bump rc to regular
Migaroez Apr 18, 2024
adc2435
Merge branch 'release/13.3.0' into v13/dev
Migaroez Apr 18, 2024
a27a4dc
Bump to next minor
Migaroez Apr 18, 2024
0980350
Add blocks in RTE telemetry (#16104)
nikolajlauridsen Apr 22, 2024
119fde2
V10: Fix for fallback file upload (#14892) (#15868)
Zeegaan Apr 24, 2024
cae106b
Fix logic for retrieving lastKnownElement
jdpnielsen Apr 19, 2024
599ec18
Implementors using Umbraco.Tests.Integration won't have to override G…
lars-erik Apr 4, 2024
5b46c71
Fix logic for retrieving lastKnownElement
jdpnielsen Apr 19, 2024
b6031de
bump version
bergmania May 3, 2024
edb516f
Bump version
bergmania May 3, 2024
fee222d
Bump version
bergmania May 3, 2024
23d0a6b
Since v13 properties can sometimes be of type IRichTextEditorIntermed…
nul800sebastiaan May 6, 2024
cfcdc9c
Webhook log improvements (#16200)
rasmusjp May 6, 2024
ba9ddd1
V13: Optimize custom MVC routing (#16218)
nikolajlauridsen May 10, 2024
1876546
V13: Optimize custom MVC routing (#16218)
nikolajlauridsen May 10, 2024
ab32bac
Property source level variation should only be applied when configure…
kjac May 13, 2024
94cef50
Property source level variation should only be applied when configure…
kjac May 13, 2024
c17d4e1
Merge pull request from GHSA-j74q-mv2c-rxmp
bergmania May 17, 2024
d8df405
Merge pull request from GHSA-j74q-mv2c-rxmp
bergmania May 17, 2024
5f24de3
Merge pull request from GHSA-j74q-mv2c-rxmp
bergmania May 17, 2024
9bacd13
Merge remote-tracking branch 'refs/remotes/origin/release/13.3' into …
bergmania May 17, 2024
0ee0db8
Merge remote-tracking branch 'refs/remotes/origin/release/10.8' into …
bergmania May 21, 2024
5c777f3
Merge remote-tracking branch 'refs/remotes/origin/v10/dev' into v11/dev
bergmania May 21, 2024
60f6560
Merge remote-tracking branch 'refs/remotes/origin/release/12.3' into …
bergmania May 21, 2024
4f382ab
Merge remote-tracking branch 'refs/remotes/origin/v11/dev' into v12/dev
bergmania May 21, 2024
04ed514
Merge remote-tracking branch 'refs/remotes/origin/v12/dev' into v13/dev
bergmania May 21, 2024
0f3160f
Move publishing notification after validation (#16331)
Zeegaan May 22, 2024
fd2138c
Bump version
bergmania May 22, 2024
eb6bb99
Ensure there is always at least 1 valid candidate (#16344)
nikolajlauridsen May 22, 2024
5f082df
Ensure there is always at least 1 valid candidate (#16344)
nikolajlauridsen May 22, 2024
696a711
Ensure ufprt-token requests are handle in the UmbracoRouteValueTransf…
bergmania May 23, 2024
5285834
Merge remote-tracking branch 'refs/remotes/origin/release/13.3' into …
bergmania May 23, 2024
5795cf1
Typo when getting query parm
jrunestone Apr 12, 2024
0aaac78
A bunch of minor performance optimizations (#16335)
bergmania Jun 3, 2024
100f2c3
Add check to ensure that RenderControllers and SurfaceControllers are…
nikolajlauridsen Jun 3, 2024
c3e7dad
bumb to rc version
Zeegaan Jun 3, 2024
46e9991
Merge branch 'v13/contrib' into v13/dev
nul800sebastiaan Jun 3, 2024
f15be3e
Merge branch 'v13/dev' into release/13.4
Zeegaan Jun 3, 2024
32912b0
Update to query to SqlRaw (#16542)
Zeegaan Jun 3, 2024
3dace4f
RTE and media picker should route medias the same way in the Delivery…
kjac Jun 4, 2024
ae7db56
Decreased retry count (#16554)
andr317c Jun 4, 2024
78bf04e
bump version.json
Zeegaan Jun 4, 2024
fc951ee
Merge branch 'release/13.4' into v13/dev
Zeegaan Jun 6, 2024
5f8eac0
bump version.json
Zeegaan Jun 6, 2024
5ae5fe3
V13: Set request culture for VirtualPageController (#16572)
nikolajlauridsen Jun 7, 2024
5e31fde
V13 QA Updated depedencies (#16606)
andr317c Jun 17, 2024
65c76fc
Decreased to 1 retry for this file only (#16623)
andr317c Jun 19, 2024
4cfa021
Merged v13/dev into v14
bergmania Jun 25, 2024
bda23c3
Merge remote-tracking branch 'refs/remotes/origin/v14/dev' into v14/f…
bergmania Jun 25, 2024
9ad67e2
Fixed test by moving when the publishing notification is fired.
bergmania Jun 25, 2024
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
2 changes: 1 addition & 1 deletion src/Umbraco.Core/DeliveryApi/ApiMediaUrlProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public string GetUrl(IPublishedContent media)
throw new ArgumentException("Media URLs can only be generated from Media items.", nameof(media));
}

return _publishedUrlProvider.GetMediaUrl(media, UrlMode.Relative);
return _publishedUrlProvider.GetMediaUrl(media);
}
}
33 changes: 33 additions & 0 deletions src/Umbraco.Core/Routing/IPublishedRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,37 @@ public interface IPublishedRouter
/// </para>
/// </remarks>
Task<IPublishedRequest> UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent);

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest and VariationContext accordingly.
/// <remarks>
/// <para>
/// This method is used for VirtualPage routing.
/// </para>
/// <para>
/// In this case we do not want to run the entire routing pipeline since ContentFinders are not needed here.
/// However, we do want to set the culture on VariationContext and PublishedRequest to the values specified by the domains.
/// </para>
/// </remarks>
/// </summary>
/// <param name="request">The request to update the culture on domain on</param>
/// <returns>True if a domain was found otherwise false.</returns>
bool RouteDomain(IPublishedRequestBuilder request) => false;

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the VariationContext accordingly.
/// </summary>
/// <remarks>
/// <para>
/// This is used for VirtualPage routing.
/// </para>
/// <para>
/// This is required to set the culture on VariationContext to the values specified by the domains, before the FindContent method is called.
/// In order to allow the FindContent implementer to correctly find content based off the culture. Before the PublishedRequest is built.
/// </para>
/// </remarks>
/// <param name="uri">The URI to resolve the domain from.</param>
/// <returns>True if a domain was found, otherwise false.</returns>
bool UpdateVariationContext(Uri uri) => false;

}
43 changes: 33 additions & 10 deletions src/Umbraco.Core/Routing/PublishedRouter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;

Check notice on line 1 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 7.06 to 6.19, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
Expand Down Expand Up @@ -108,7 +108,7 @@
// find domain
if (builder.Domain == null)
{
FindDomain(builder);
FindAndSetDomain(builder);
}

await RouteRequestInternalAsync(builder);
Expand Down Expand Up @@ -185,7 +185,7 @@

private async Task<IPublishedRequest> TryRouteRequest(IPublishedRequestBuilder request)
{
FindDomain(request);
FindAndSetDomain(request);

if (request.IsRedirect())
{
Expand Down Expand Up @@ -270,55 +270,78 @@
// to find out the appropriate template
}

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a domain was found.</returns>
internal bool FindDomain(IPublishedRequestBuilder request)
/// <inheritdoc />
public bool RouteDomain(IPublishedRequestBuilder request)
{
var found = FindAndSetDomain(request);
HandleWildcardDomains(request);
SetVariationContext(request.Culture);
return found;
}

/// <inheritdoc />
public bool UpdateVariationContext(Uri uri)
{
DomainAndUri? domain = FindDomain(uri, out _);
SetVariationContext(domain?.Culture);
return domain?.Culture is not null;
}

private DomainAndUri? FindDomain(Uri uri, out string? defaultCulture)
{
const string tracePrefix = "FindDomain: ";

// note - we are not handling schemes nor ports here.
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
_logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, uri);
}

IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
IDomainCache? domainsCache = umbracoContext.PublishedSnapshot.Domains;
var domains = domainsCache?.GetAll(false).ToList();

// determines whether a domain corresponds to a published document, since some
// domains may exist but on a document that has been unpublished - as a whole - or
// that is not published for the domain's culture - in which case the domain does
// not apply
bool IsPublishedContentDomain(Domain domain)
{
// just get it from content cache - optimize there, not here
IPublishedContent? domainDocument = umbracoContext.PublishedSnapshot.Content?.GetById(domain.ContentId);

// not published - at all
if (domainDocument == null)
{
return false;
}

// invariant - always published
if (!domainDocument.ContentType.VariesByCulture())
{
return true;
}

// variant, ensure that the culture corresponding to the domain's language is published
return domain.Culture is not null && domainDocument.Cultures.ContainsKey(domain.Culture);
}

domains = domains?.Where(IsPublishedContentDomain).ToList();

var defaultCulture = domainsCache?.DefaultCulture;
defaultCulture = domainsCache?.DefaultCulture;

return DomainUtilities.SelectDomain(domains, uri, defaultCulture: defaultCulture);
}

/// <summary>
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
/// </summary>
/// <returns>A value indicating whether a domain was found.</returns>
internal bool FindAndSetDomain(IPublishedRequestBuilder request)
{
const string tracePrefix = "FindDomain: ";
// try to find a domain matching the current request
DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
DomainAndUri? domainAndUri = FindDomain(request.Uri, out var defaultCulture);

Check notice on line 344 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ No longer an issue: Bumpy Road Ahead

FindDomain is no longer above the threshold for logical blocks with deeply nested code. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check notice on line 344 in src/Umbraco.Core/Routing/PublishedRouter.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ New issue: Bumpy Road Ahead

FindAndSetDomain has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

// handle domain - always has a contentId and a culture
if (domainAndUri != null)
Expand Down
98 changes: 67 additions & 31 deletions src/Umbraco.Core/Services/ContentService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;

Check notice on line 1 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Lines of Code in a Single File

The lines of code increases from 2170 to 2194, improve code health by reducing it to 1000. The number of Lines of Code in a single file. More Lines of Code lowers the code health.

Check notice on line 1 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Overall Code Complexity

The mean cyclomatic complexity increases from 4.12 to 4.19, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
Expand Down Expand Up @@ -1140,66 +1140,74 @@
throw new ArgumentException("Cultures cannot be null or whitespace", nameof(cultures));
}

EventMessages evtMsgs = EventMessagesFactory.Get();

// we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications
if (HasUnsavedChanges(content))
{
return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, EventMessagesFactory.Get(), content);
}

if (content.Name != null && content.Name.Length > 255)
{
throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
}

PublishedState publishedState = content.PublishedState;
if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
{
throw new InvalidOperationException(
$"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method.");
}

// cannot accept invariant (null or empty) culture for variant content type
// cannot accept a specific culture for invariant content type (but '*' is ok)
if (content.ContentType.VariesByCulture())
{
if (cultures.Length > 1 && cultures.Contains("*"))
{
throw new ArgumentException("Cannot combine wildcard and specific cultures when publishing variant content types.", nameof(cultures));
}
}
else
{
if (cultures.Length == 0)
{
cultures = new[] { "*" };
}

if (cultures[0] != "*" || cultures.Length > 1)
{
throw new ArgumentException($"Only wildcard culture is supported when publishing invariant content types.", nameof(cultures));
}
}

using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
scope.WriteLock(Constants.Locks.ContentTree);

var allLangs = _languageRepository.GetMany().ToList();

EventMessages evtMsgs = EventMessagesFactory.Get();

// this will create the correct culture impact even if culture is * or null
IEnumerable<CultureImpact?> impacts =
cultures.Select(culture => _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content));

// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
foreach (CultureImpact? impact in impacts)
{
content.PublishCulture(impact);
}

PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
// Change state to publishing
content.PublishedState = PublishedState.Publishing;
var publishingNotification = new ContentPublishingNotification(content, evtMsgs);
if (scope.Notifications.PublishCancelable(publishingNotification))
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}

PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, publishingNotification.State, userId);

Check warning on line 1210 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

Publish increases in cyclomatic complexity from 17 to 18, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
scope.Complete();
return result;
}
Expand Down Expand Up @@ -1254,29 +1262,35 @@

var allLangs = _languageRepository.GetMany().ToList();

var savingNotification = new ContentSavingNotification(content, evtMsgs);
if (scope.Notifications.PublishCancelable(savingNotification))
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}

// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
// Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
// essentially be re-publishing the document with the requested culture removed
// We are however unpublishing all cultures, so we will set this to unpublishing.
content.UnpublishCulture(culture);
content.PublishedState = PublishedState.Unpublishing;
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
scope.Complete();
return result;
}
else
{
// Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
// essentially be re-publishing the document with the requested culture removed.
// The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
// and will then unpublish the document accordingly.
// If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
var removed = content.UnpublishCulture(culture);

// Save and publish any changes
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);

Check warning on line 1293 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

Unpublish increases in cyclomatic complexity from 14 to 15, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

scope.Complete();

Expand Down Expand Up @@ -1327,9 +1341,15 @@

scope.WriteLock(Constants.Locks.ContentTree);

var savingNotification = new ContentSavingNotification(content, evtMsgs);
if (scope.Notifications.PublishCancelable(savingNotification))
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}

var allLangs = _languageRepository.GetMany().ToList();

PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, out _);
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId);
scope.Complete();
return result;
}
Expand Down Expand Up @@ -1359,259 +1379,267 @@
IContent content,
EventMessages eventMessages,
IReadOnlyCollection<ILanguage> allLangs,
IDictionary<string, object?>? notificationState,
int userId,
out IDictionary<string, object?>? initialNotificationState,
bool branchOne = false,
bool branchRoot = false)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}

if (content == null)
{
throw new ArgumentNullException(nameof(content));
}

if (eventMessages == null)
{
throw new ArgumentNullException(nameof(eventMessages));
}

PublishResult? publishResult = null;
PublishResult? unpublishResult = null;

// nothing set = republish it all
if (content.PublishedState != PublishedState.Publishing &&
content.PublishedState != PublishedState.Unpublishing)
{
content.PublishedState = PublishedState.Publishing;
}

// State here is either Publishing or Unpublishing
// Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
var publishing = content.PublishedState == PublishedState.Publishing;
var unpublishing = content.PublishedState == PublishedState.Unpublishing;

var variesByCulture = content.ContentType.VariesByCulture();

// Track cultures that are being published, changed, unpublished
IReadOnlyList<string>? culturesPublishing = null;
IReadOnlyList<string>? culturesUnpublishing = null;
IReadOnlyList<string>? culturesChanging = variesByCulture
? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
: null;

var isNew = !content.HasIdentity;
TreeChangeTypes changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;

// Inline method to persist the document with the documentRepository since this logic could be called a couple times below
void SaveDocument(IContent c)
{
// save, always
if (c.HasIdentity == false)
{
c.CreatorId = userId;
}

c.WriterId = userId;

// saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
_documentRepository.Save(c);
}

initialNotificationState = null;
if (publishing)
{
// Determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
culturesUnpublishing = content.GetCulturesUnpublishing();
culturesPublishing = variesByCulture
? content.PublishCultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
: null;

// ensure that the document can be published, and publish handling events, business rules, etc
publishResult = StrategyCanPublish(
scope,
content, /*checkPath:*/
!branchOne || branchRoot,
culturesPublishing,
culturesUnpublishing,
eventMessages,
allLangs,
out initialNotificationState);
notificationState);

if (publishResult.Success)
{
// raise Publishing notification
if (scope.Notifications.PublishCancelable(
new ContentPublishingNotification(content, eventMessages).WithState(notificationState)))
{
_logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, eventMessages, content);
}

// note: StrategyPublish flips the PublishedState to Publishing!
publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages);

// Check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
if (publishResult.Result == PublishResultType.SuccessUnpublishCulture &&
content.PublishCultureInfos?.Count == 0)
{
// This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
// so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
// the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
// persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
// mark the document for Unpublishing.
SaveDocument(content);

// Set the flag to unpublish and continue
unpublishing = content.Published; // if not published yet, nothing to do
}
}
else
{
// in a branch, just give up
if (branchOne && !branchRoot)
{
return publishResult;
}

// Check for mandatory culture missing, and then unpublish document as a whole
if (publishResult.Result == PublishResultType.FailedPublishMandatoryCultureMissing)
{
publishing = false;
unpublishing = content.Published; // if not published yet, nothing to do

// we may end up in a state where we won't publish nor unpublish
// keep going, though, as we want to save anyways
}

// reset published state from temp values (publishing, unpublishing) to original value
// (published, unpublished) in order to save the document, unchanged - yes, this is odd,
// but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
// PublishState to anything other than Publishing or Unpublishing - which is precisely
// what we want to do here - throws
content.Published = content.Published;
}
}

// won't happen in a branch
if (unpublishing)
{
IContent? newest = GetById(content.Id); // ensure we have the newest version - in scope
if (content.VersionId != newest?.VersionId)
{
return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, content);
}

if (content.Published)
{
// ensure that the document can be unpublished, and unpublish
// handling events, business rules, etc
// note: StrategyUnpublish flips the PublishedState to Unpublishing!
// note: This unpublishes the entire document (not different variants)
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages, out initialNotificationState);
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages, notificationState);
if (unpublishResult.Success)
{
unpublishResult = StrategyUnpublish(content, eventMessages);
}
else
{
// reset published state from temp values (publishing, unpublishing) to original value
// (published, unpublished) in order to save the document, unchanged - yes, this is odd,
// but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
// PublishState to anything other than Publishing or Unpublishing - which is precisely
// what we want to do here - throws
content.Published = content.Published;
return unpublishResult;
}
}
else
{
// already unpublished - optimistic concurrency collision, really,
// and I am not sure at all what we should do, better die fast, else
// we may end up corrupting the db
throw new InvalidOperationException("Concurrency collision.");
}
}

// Persist the document
SaveDocument(content);

// we have tried to unpublish - won't happen in a branch
if (unpublishing)
{
// and succeeded, trigger events
if (unpublishResult?.Success ?? false)
{
// events and audit
scope.Notifications.Publish(
new ContentUnpublishedNotification(content, eventMessages).WithState(initialNotificationState));
new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));

if (culturesUnpublishing != null)
{
// This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
var langs = string.Join(", ", allLangs
.Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
.Select(x => x.CultureName));
Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);

if (publishResult == null)
{
throw new PanicException("publishResult == null - should not happen");
}

switch (publishResult.Result)
{
case PublishResultType.FailedPublishMandatoryCultureMissing:
// Occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)

// Log that the whole content item has been unpublished due to mandatory culture unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, eventMessages, content);
case PublishResultType.SuccessUnpublishCulture:
// Occurs when the last culture is unpublished
Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)");
return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, content);
}
}

Audit(AuditType.Unpublish, userId, content.Id);
return new PublishResult(PublishResultType.SuccessUnpublish, eventMessages, content);
}

// or, failed
scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
return new PublishResult(PublishResultType.FailedUnpublish, eventMessages, content); // bah
}

// we have tried to publish
if (publishing)
{
// and succeeded, trigger events
if (publishResult?.Success ?? false)
{
if (isNew == false && previouslyPublished == false)
{
changeType = TreeChangeTypes.RefreshBranch; // whole branch
}
else if (isNew == false && previouslyPublished)
{
changeType = TreeChangeTypes.RefreshNode; // single node
}

// invalidate the node/branch
// for branches, handled by SaveAndPublishBranch
if (!branchOne)
{
scope.Notifications.Publish(
new ContentTreeChangeNotification(content, changeType, eventMessages));
scope.Notifications.Publish(
new ContentPublishedNotification(content, eventMessages).WithState(initialNotificationState));
new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
}

// it was not published and now is... descendants that were 'published' (but
// had an unpublished ancestor) are 're-published' ie not explicitly published
// but back as 'published' nevertheless
if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id))
{
IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray();
scope.Notifications.Publish(
new ContentPublishedNotification(descendants, eventMessages).WithState(initialNotificationState));
new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState));

Check warning on line 1642 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

CommitDocumentChangesInternal increases in cyclomatic complexity from 52 to 53, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

switch (publishResult.Result)
Expand Down Expand Up @@ -1711,17 +1739,24 @@
continue; // shouldn't happen but no point in processing this document if there's nothing there
}

var savingNotification = new ContentSavingNotification(d, evtMsgs);
if (scope.Notifications.PublishCancelable(savingNotification))
{
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
continue;
}

foreach (var c in pendingCultures)
{
// Clear this schedule for this culture
contentSchedule.Clear(c, ContentScheduleAction.Expire, date);

// set the culture to be published
d.UnpublishCulture(c);
}

_documentRepository.PersistContentSchedule(d, contentSchedule);
PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId, out _);
PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);

Check warning on line 1759 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

PerformScheduledPublishingExpiration increases in cyclomatic complexity from 9 to 11, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
if (result.Success == false)
{
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
Expand Down Expand Up @@ -1775,52 +1810,59 @@
{
continue; // shouldn't happen but no point in processing this document if there's nothing there
}
var savingNotification = new ContentSavingNotification(d, evtMsgs);
if (scope.Notifications.PublishCancelable(savingNotification))
{
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
continue;
}


var publishing = true;
foreach (var culture in pendingCultures)
{
// Clear this schedule for this culture
contentSchedule.Clear(culture, ContentScheduleAction.Release, date);

if (d.Trashed)
{
continue; // won't publish
}

// publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
IProperty[]? invalidProperties = null;
var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture));
var tryPublish = d.PublishCulture(impact) &&
_propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
if (invalidProperties != null && invalidProperties.Length > 0)
{
_logger.LogWarning(
"Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
d.Id,
culture,
string.Join(",", invalidProperties.Select(x => x.Alias)));
}

publishing &= tryPublish; // set the culture to be published
if (!publishing)
{
}
}

PublishResult result;

if (d.Trashed)
{
result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
}
else if (!publishing)
{
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
}
else
{
_documentRepository.PersistContentSchedule(d, contentSchedule);
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId, out _);
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId);

Check warning on line 1865 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

❌ Getting worse: Complex Method

PerformScheduledPublishingRelease increases in cyclomatic complexity from 18 to 20, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

if (result.Success == false)
Expand Down Expand Up @@ -2162,14 +2204,20 @@
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
}

var savingNotification = new ContentSavingNotification(document, evtMsgs);
if (scope.Notifications.PublishCancelable(savingNotification))
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
}

// publish & check if values are valid
if (!publishCultures(document, culturesToPublish, allLangs))
{
// TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}

PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, userId, out initialNotificationState, true, isRoot);
PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot);
if (result.Success)
{
publishedDocuments.Add(document);
Expand Down Expand Up @@ -3018,19 +3066,8 @@
IReadOnlyCollection<string>? culturesUnpublishing,
EventMessages evtMsgs,
IReadOnlyCollection<ILanguage> allLangs,
out IDictionary<string, object?>? initialNotificationState)
IDictionary<string, object?>? notificationState)
{

Check notice on line 3070 in src/Umbraco.Core/Services/ContentService.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ Getting better: Complex Method

StrategyCanPublish decreases in cyclomatic complexity from 31 to 30, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
// raise Publishing notification
var notification = new ContentPublishingNotification(content, evtMsgs);
var notificationResult = scope.Notifications.PublishCancelable(notification);
initialNotificationState = notification.State;

if (notificationResult)
{
_logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled");
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}

var variesByCulture = content.ContentType.VariesByCulture();

// If it's null it's invariant
Expand Down Expand Up @@ -3268,12 +3305,11 @@
ICoreScope scope,
IContent content,
EventMessages evtMsgs,
out IDictionary<string, object?>? initialNotificationState)
IDictionary<string, object?>? notificationState)
{
// raise Unpublishing notification
var notification = new ContentUnpublishingNotification(content, evtMsgs);
var notification = new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState);
var notificationResult = scope.Notifications.PublishCancelable(notification);
initialNotificationState = notification.State;

if (notificationResult)
{
Expand Down
Loading
Loading