Merge branch 'v15/dev' into v15/bugfix/intermittent-issue-rendering-granular-permissions-for-user-group
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the content notification settings.
|
||||
@@ -141,4 +142,10 @@ public class ContentSettings
|
||||
/// </summary>
|
||||
[DefaultValue(StaticShowDomainWarnings)]
|
||||
public bool ShowDomainWarnings { get; set; } = StaticShowDomainWarnings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show unroutable content warnings.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticShowUnroutableContentWarnings)]
|
||||
public bool ShowUnroutableContentWarnings { get; set; } = StaticShowUnroutableContentWarnings;
|
||||
}
|
||||
|
||||
@@ -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<ContentPublishedNotification>
|
||||
{
|
||||
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> 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<IContent>(),
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1171,10 +1171,14 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : 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<TItem>
|
||||
{
|
||||
Items = allowedChildren.Take(take).Skip(skip),
|
||||
Items = allowedChildren.OrderBy(x => sortedKeys.IndexOf(x.Key)).Take(take).Skip(skip),
|
||||
Total = allowedChildren.Length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -411,8 +411,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, AuditNotificationsHandler>();
|
||||
|
||||
// Handlers for publish warnings
|
||||
builder
|
||||
.AddNotificationHandler<ContentPublishedNotification, AddDomainWarningsWhenPublishingNotificationHandler>();
|
||||
builder.AddNotificationHandler<ContentPublishedNotification, AddDomainWarningsWhenPublishingNotificationHandler>();
|
||||
builder.AddNotificationAsyncHandler<ContentPublishedNotification, AddUnroutableContentWarningsWhenPublishingNotificationHandler>();
|
||||
|
||||
// Handlers for save warnings
|
||||
builder
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -37,6 +37,13 @@ export class UmbRouteContext extends UmbContextBase<UmbRouteContext> {
|
||||
});
|
||||
}
|
||||
|
||||
getBasePath() {
|
||||
return this.#basePath.getValue();
|
||||
}
|
||||
getActivePath() {
|
||||
return this.getBasePath() + '/' + this.#activeLocalPath;
|
||||
}
|
||||
|
||||
public registerModal(registration: UmbModalRouteRegistration) {
|
||||
this.#modalRegistrations.push(registration);
|
||||
this.#createNewUrlBuilder(registration);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -198,7 +198,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
if (!item.unique) return;
|
||||
const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique });
|
||||
return html`
|
||||
<uui-ref-node-document-type name=${this.localize.string(item.name)} href=${href}>
|
||||
<uui-ref-node-document-type id=${item.unique} name=${this.localize.string(item.name)} href=${href}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this.#removeItem(item)} label=${this.localize.term('general_remove')}></uui-button>
|
||||
|
||||
@@ -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<UmbCrop> = [];
|
||||
|
||||
@property({ type: Array })
|
||||
public set value(value: Array<UmbCrop>) {
|
||||
this._value = value ?? [];
|
||||
this.#sorter.setModel(this.value);
|
||||
}
|
||||
public get value(): Array<UmbCrop> {
|
||||
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`
|
||||
<div class="crop">
|
||||
<div class="crop" data-alias="${item.alias}">
|
||||
<span class="crop-drag">+</span>
|
||||
<span><strong>${item.label}</strong> <em>(${item.alias})</em></span>
|
||||
<span class="crop-size">(${item.width} x ${item.height}px)</span>
|
||||
|
||||
Reference in New Issue
Block a user