From f3b09f3489ebb2202cf5bbf9e45e916e7fc640c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 7 Jan 2025 13:42:41 +0100 Subject: [PATCH 1/5] remove circular dependency --- src/Umbraco.Web.UI.Client/src/apps/app/app-logo.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/apps/app/app-logo.element.ts b/src/Umbraco.Web.UI.Client/src/apps/app/app-logo.element.ts index 446e123028..6c885e274f 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/app/app-logo.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/app/app-logo.element.ts @@ -1,4 +1,4 @@ -import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; +import { UMB_APP_CONTEXT } from './app.context.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit'; From 376c49659720aaf62103d797fd3783be0080af2d Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 7 Jan 2025 14:01:11 +0100 Subject: [PATCH 2/5] Ensure sort order is used when rendering the list of allowed documents that can be created under an item in the content section, and allowed for sorting of selected allowed content types (#17875) * Ensure sort order is used when rendering the list of allowed documents that can be created under an item in the content section. * Fixed issue preventing sorting of the allowed content types. --- .../ContentTypeServiceBaseOfTRepositoryTItemTService.cs | 8 ++++++-- .../input-document-type/input-document-type.element.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index d458a17fbb..74fe09f8c8 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -1171,10 +1171,14 @@ public abstract class ContentTypeServiceBase : ContentTypeSe } else { - TItem[] allowedChildren = GetMany(parent.AllowedContentTypes.Select(x => x.Key)).ToArray(); + // Get the sorted keys. Whilst we can't guarantee the order that comes back from GetMany, we can use + // this to sort the resulting list of allowed children. + Guid[] sortedKeys = parent.AllowedContentTypes.OrderBy(x => x.SortOrder).Select(x => x.Key).ToArray(); + + TItem[] allowedChildren = GetMany(sortedKeys).ToArray(); result = new PagedModel { - Items = allowedChildren.Take(take).Skip(skip), + Items = allowedChildren.OrderBy(x => sortedKeys.IndexOf(x.Key)).Take(take).Skip(skip), Total = allowedChildren.Length, }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index c52b7fde80..a091a221b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -198,7 +198,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin + ${this.#renderIcon(item)} this.#removeItem(item)} label=${this.localize.term('general_remove')}> From 79c8652ca4217a2865e5539967960d02b6fa0a59 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 7 Jan 2025 19:15:58 +0100 Subject: [PATCH 3/5] Implemented sorting for media picker crops (#17879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implemented sorting for media picker crops. * Removed unused type import. --------- Co-authored-by: Niels Lyngsø --- .../property-editor-ui-image-crops.element.ts | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts index 54ff9f9b0f..74728a089b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/image-crops/property-editor-ui-image-crops.element.ts @@ -4,6 +4,7 @@ import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/propert import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; export type UmbCrop = { label: string; @@ -20,14 +21,42 @@ export class UmbPropertyEditorUIImageCropsElement extends UmbLitElement implemen @query('#label') private _labelInput!: HTMLInputElement; - @property({ attribute: false }) - value: UmbCrop[] = []; + @state() + private _value: Array = []; + + @property({ type: Array }) + public set value(value: Array) { + this._value = value ?? []; + this.#sorter.setModel(this.value); + } + public get value(): Array { + return this._value; + } @state() editCropAlias = ''; #oldInputValue = ''; + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element: HTMLElement) => { + const unique = element.dataset["alias"]; + return unique; + }, + getUniqueOfModel: (modelEntry: UmbCrop) => { + return modelEntry.alias; + }, + identifier: 'Umb.SorterIdentifier.ImageCrops', + itemSelector: '.crop', + containerSelector: '.crops', + onChange: ({ model }) => { + const oldValue = this._value; + this._value = model; + this.requestUpdate('_value', oldValue); + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + }, + }); + #onRemove(alias: string) { this.value = [...this.value.filter((item) => item.alias !== alias)]; this.dispatchEvent(new UmbPropertyValueChangeEvent()); @@ -163,7 +192,7 @@ export class UmbPropertyEditorUIImageCropsElement extends UmbLitElement implemen this.value, (item) => item.alias, (item) => html` -
+
+ ${item.label} (${item.alias}) (${item.width} x ${item.height}px) From bf9ba16039cf28de3c621d760cb4472ec6707b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 7 Jan 2025 19:26:10 +0100 Subject: [PATCH 4/5] Add workspace view path const (#17880) * use path const for workspace view * get base path and active path from router context --------- Co-authored-by: Kenn Jacobsen --- .../src/packages/core/router/route.context.ts | 7 +++++++ .../workspace-editor/workspace-editor.element.ts | 4 ++-- .../src/packages/core/workspace/paths.ts | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts index 6ba9b8846e..a5ed6a86c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts @@ -37,6 +37,13 @@ export class UmbRouteContext extends UmbContextBase { }); } + getBasePath() { + return this.#basePath.getValue(); + } + getActivePath() { + return this.getBasePath() + '/' + this.#activeLocalPath; + } + public registerModal(registration: UmbModalRouteRegistration) { this.#modalRegistrations.push(registration); this.#createNewUrlBuilder(registration); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts index d9f922996a..5c59f06a66 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts @@ -3,7 +3,7 @@ import { createExtensionElement, UmbExtensionsManifestInitializer } from '@umbra import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_VIEW_PATH_PATTERN, type ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace'; import type { UmbRoute, UmbRouterSlotInitEvent, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router'; /** @@ -62,7 +62,7 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { if (this._workspaceViews.length > 0) { newRoutes = this._workspaceViews.map((manifest) => { return { - path: `view/${manifest.meta.pathname}`, + path: UMB_WORKSPACE_VIEW_PATH_PATTERN.generateLocal({ viewPathname: manifest.meta.pathname }), component: () => createExtensionElement(manifest), setup: (component) => { if (component) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts index da4f96e16a..8ac71ae068 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/paths.ts @@ -5,3 +5,8 @@ export const UMB_WORKSPACE_PATH_PATTERN = new UmbPathPattern< { entityType: string }, typeof UMB_SECTION_PATH_PATTERN.ABSOLUTE_PARAMS >('workspace/:entityType', UMB_SECTION_PATH_PATTERN); + +export const UMB_WORKSPACE_VIEW_PATH_PATTERN = new UmbPathPattern< + { viewPathname: string }, + typeof UMB_WORKSPACE_PATH_PATTERN.ABSOLUTE_PARAMS +>('view/:viewPathname', UMB_WORKSPACE_PATH_PATTERN); From e5e5e8cb11147b7417258d0883743e444f75df29 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 8 Jan 2025 09:14:14 +0100 Subject: [PATCH 5/5] V15: Warn when content is unroutable (#17837) * Add notification handler * Add IsContentPublished that bypasses caching * Add clarifying comment * Refactor, to never hit the db * Remove old comment * Don't add warnings if disabled * Dedicated configuration option to suppress unroutable content warnings --------- Co-authored-by: kjac --- .../Configuration/Models/ContentSettings.cs | 7 ++ ...rningsWhenPublishingNotificationHandler.cs | 119 ++++++++++++++++++ .../Routing/NewDefaultUrlProvider.cs | 19 ++- .../UmbracoBuilder.CoreServices.cs | 4 +- 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Core/Events/AddUnroutableContentWarningsWhenPublishingNotificationHandler.cs diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 2d2ec674b9..28c9ad1427 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -28,6 +28,7 @@ public class ContentSettings internal const bool StaticDisableUnpublishWhenReferenced = false; internal const bool StaticAllowEditInvariantFromNonDefault = false; internal const bool StaticShowDomainWarnings = true; + internal const bool StaticShowUnroutableContentWarnings = true; /// /// Gets or sets a value for the content notification settings. @@ -141,4 +142,10 @@ public class ContentSettings /// [DefaultValue(StaticShowDomainWarnings)] public bool ShowDomainWarnings { get; set; } = StaticShowDomainWarnings; + + /// + /// Gets or sets a value indicating whether to show unroutable content warnings. + /// + [DefaultValue(StaticShowUnroutableContentWarnings)] + public bool ShowUnroutableContentWarnings { get; set; } = StaticShowUnroutableContentWarnings; } diff --git a/src/Umbraco.Core/Events/AddUnroutableContentWarningsWhenPublishingNotificationHandler.cs b/src/Umbraco.Core/Events/AddUnroutableContentWarningsWhenPublishingNotificationHandler.cs new file mode 100644 index 0000000000..ad457d287c --- /dev/null +++ b/src/Umbraco.Core/Events/AddUnroutableContentWarningsWhenPublishingNotificationHandler.cs @@ -0,0 +1,119 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Navigation; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Events; + +public class AddUnroutableContentWarningsWhenPublishingNotificationHandler : INotificationAsyncHandler +{ + private readonly IPublishedRouter _publishedRouter; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILanguageService _languageService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IContentService _contentService; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly ILoggerFactory _loggerFactory; + private readonly UriUtility _uriUtility; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IPublishedContentCache _publishedContentCache; + private readonly IDocumentNavigationQueryService _navigationQueryService; + private readonly IEventMessagesFactory _eventMessagesFactory; + private readonly ContentSettings _contentSettings; + + public AddUnroutableContentWarningsWhenPublishingNotificationHandler( + IPublishedRouter publishedRouter, + IUmbracoContextAccessor umbracoContextAccessor, + ILanguageService languageService, + ILocalizedTextService localizedTextService, + IContentService contentService, + IVariationContextAccessor variationContextAccessor, + ILoggerFactory loggerFactory, + UriUtility uriUtility, + IPublishedUrlProvider publishedUrlProvider, + IPublishedContentCache publishedContentCache, + IDocumentNavigationQueryService navigationQueryService, + IEventMessagesFactory eventMessagesFactory, + IOptions contentSettings) + { + _publishedRouter = publishedRouter; + _umbracoContextAccessor = umbracoContextAccessor; + _languageService = languageService; + _localizedTextService = localizedTextService; + _contentService = contentService; + _variationContextAccessor = variationContextAccessor; + _loggerFactory = loggerFactory; + _uriUtility = uriUtility; + _publishedUrlProvider = publishedUrlProvider; + _publishedContentCache = publishedContentCache; + _navigationQueryService = navigationQueryService; + _eventMessagesFactory = eventMessagesFactory; + _contentSettings = contentSettings.Value; + } + + public async Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken) + { + if (_contentSettings.ShowUnroutableContentWarnings is false) + { + return; + } + + foreach (IContent content in notification.PublishedEntities) + { + string[]? successfulCultures; + if (content.ContentType.VariesByCulture() is false) + { + // successfulCultures will be null here - change it to a wildcard and utilize this below + successfulCultures = ["*"]; + } + else + { + successfulCultures = content.PublishedCultures.ToArray(); + } + + if (successfulCultures?.Any() is not true) + { + return; + } + + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) + { + return; + } + + UrlInfo[] urls = (await content.GetContentUrlsAsync( + _publishedRouter, + umbracoContext, + _languageService, + _localizedTextService, + _contentService, + _variationContextAccessor, + _loggerFactory.CreateLogger(), + _uriUtility, + _publishedUrlProvider, + _publishedContentCache, + _navigationQueryService)).ToArray(); + + + EventMessages eventMessages = _eventMessagesFactory.Get(); + foreach (var culture in successfulCultures) + { + if (urls.Where(u => u.Culture == culture || culture == "*").All(u => u.IsUrl is false)) + { + eventMessages.Add(new EventMessage("Content published", "The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.", EventMessageType.Warning)); + + // only add one warning here, even though there might actually be more + break; + } + } + } + } +} diff --git a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs index f7eeb1a1b4..3818bc2776 100644 --- a/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs @@ -145,8 +145,10 @@ public class NewDefaultUrlProvider : IUrlProvider private string GetLegacyRouteFormatById(Guid key, string? culture) { + var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode; - return _documentUrlService.GetLegacyRouteFormat(key, culture, _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode); + + return _documentUrlService.GetLegacyRouteFormat(key, culture, isDraft); } @@ -163,9 +165,22 @@ public class NewDefaultUrlProvider : IUrlProvider throw new ArgumentException("Current URL must be absolute.", nameof(current)); } + // This might seem to be some code duplication, as we do the same check in GetLegacyRouteFormat + // but this is strictly neccesary, as if we're coming from a published notification + // this document will still not always be in the memory cache. And thus we have to hit the DB + // We have the published content now, so we can check if the culture is published, and thus avoid the DB hit. + string route; + var isDraft = _umbracoContextAccessor.GetRequiredUmbracoContext().InPreviewMode; + if(isDraft is false && string.IsNullOrWhiteSpace(culture) is false && content.Cultures.Any() && content.IsInvariantOrHasCulture(culture) is false) + { + route = "#"; + } + else + { + route = GetLegacyRouteFormatById(content.Key, culture); + } // will not use cache if previewing - var route = GetLegacyRouteFormatById(content.Key, culture); return GetUrlFromRoute(route, content.Id, current, mode, culture); } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 50ae0d7deb..bd101ad37b 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -411,8 +411,8 @@ public static partial class UmbracoBuilderExtensions .AddNotificationHandler(); // Handlers for publish warnings - builder - .AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationAsyncHandler(); // Handlers for save warnings builder