diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts index 801babf652..1ce6295ebc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts @@ -39,7 +39,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { _dataOwner?: UmbBlockElementManager; @state() - _variantId?: UmbVariantId; + _workspaceVariantId?: UmbVariantId; @state() _visibleProperties?: Array; @@ -56,7 +56,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { this.observe( workspaceContext?.variantId, (variantId) => { - this._variantId = variantId; + this._workspaceVariantId = variantId; this.#processPropertyStructure(); }, 'observeVariantId', @@ -83,16 +83,19 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { } #processPropertyStructure() { - if (!this._dataOwner || !this.#properties || !this.#propertyStructureHelper) { + if (!this._dataOwner || !this.#properties || !this.#propertyStructureHelper || !this._workspaceVariantId) { return; } const propertyViewGuard = this._dataOwner.propertyViewGuard; this.#properties.forEach((property) => { - const propertyVariantId = new UmbVariantId(this._variantId?.culture, this._variantId?.segment); + const propertyVariantId = new UmbVariantId( + property.variesByCulture ? this._workspaceVariantId!.culture : null, + property.variesBySegment ? this._workspaceVariantId!.segment : null, + ); this.observe( - propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property), + propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property, this._workspaceVariantId!), (permitted) => { if (permitted) { this.#visiblePropertiesUniques.push(property.unique); @@ -117,7 +120,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { } override render() { - return this._variantId && this._visibleProperties + return this._workspaceVariantId && this._visibleProperties ? repeat( this._visibleProperties, (property) => property.alias, @@ -126,7 +129,7 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { class="property" .ownerContext=${this._dataOwner} .ownerEntityType=${this._ownerEntityType} - .variantId=${this._variantId} + .variantId=${this._workspaceVariantId} .property=${property}>`, ) : nothing; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts index 20ea00482b..fb592580eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-property.element.ts @@ -38,7 +38,11 @@ export class UmbBlockWorkspaceViewEditPropertyElement extends UmbLitElement { })}].value`; this.observe( - this.ownerContext.propertyWriteGuard.isPermittedForVariantAndProperty(propertyVariantId, this.property), + this.ownerContext.propertyWriteGuard.isPermittedForVariantAndProperty( + propertyVariantId, + this.property, + this.variantId, + ), (write) => { this._writeable = write; }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts index 0e5fc652a9..797722426c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-properties.element.ts @@ -29,7 +29,7 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement } @state() - _variantId?: UmbVariantId; + _datasetVariantId?: UmbVariantId; @state() _visibleProperties?: Array; @@ -38,7 +38,7 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement super(); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (datasetContext) => { - this._variantId = datasetContext?.getVariantId(); + this._datasetVariantId = datasetContext?.getVariantId(); this.#processPropertyStructure(); }); @@ -61,16 +61,19 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement } #processPropertyStructure() { - if (!this.#workspaceContext || !this.#properties || !this.#propertyStructureHelper) { + if (!this.#workspaceContext || !this.#properties || !this.#propertyStructureHelper || !this._datasetVariantId) { return; } const propertyViewGuard = this.#workspaceContext.propertyViewGuard; this.#properties.forEach((property) => { - const propertyVariantId = new UmbVariantId(this._variantId?.culture, this._variantId?.segment); + const propertyVariantId = new UmbVariantId( + property.variesByCulture ? this._datasetVariantId?.culture : null, + property.variesBySegment ? this._datasetVariantId?.segment : null, + ); this.observe( - propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property), + propertyViewGuard.isPermittedForVariantAndProperty(propertyVariantId, property, this._datasetVariantId!), (permitted) => { if (permitted) { this.#visiblePropertiesUniques.push(property.unique); @@ -95,14 +98,14 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement } override render() { - return this._variantId && this._visibleProperties + return this._datasetVariantId && this._visibleProperties ? repeat( this._visibleProperties, (property) => property.alias, (property) => html``, ) : nothing; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts index b7f3396900..eb502db256 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/views/edit/content-editor-property.element.ts @@ -46,7 +46,11 @@ export class UmbContentWorkspaceViewEditPropertyElement extends UmbLitElement { })}].value`; this.observe( - this._context.propertyWriteGuard.isPermittedForVariantAndProperty(propertyVariantId, this.property), + this._context.propertyWriteGuard.isPermittedForVariantAndProperty( + propertyVariantId, + this.property, + this.variantId, + ), (write) => { this._writeable = write; }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts index e4a95e3ca2..a0f04cc985 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.test.ts @@ -70,7 +70,7 @@ describe('UmbVariantPropertyGuardManager', () => { it('is not permitted for a variant when no states', (done) => { manager - .isPermittedForVariantAndProperty(invariantVariant, propB) + .isPermittedForVariantAndProperty(invariantVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -82,7 +82,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleEn); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.true; done(); @@ -94,7 +94,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleInv); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -106,7 +106,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(statePropAInv); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -117,7 +117,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(statePropAInv); manager - .isPermittedForVariantAndProperty(invariantVariant, propB) + .isPermittedForVariantAndProperty(invariantVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -129,7 +129,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(statePropAInv); manager - .isPermittedForVariantAndProperty(englishVariant, propA) + .isPermittedForVariantAndProperty(englishVariant, propA, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -141,7 +141,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(rulePlain); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.true; done(); @@ -154,7 +154,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleNoEn); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -166,7 +166,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleNoPlain); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -179,7 +179,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleEn); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -193,7 +193,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(ruleNoEn); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -206,7 +206,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(rulePlain); manager - .isPermittedForVariantAndProperty(englishVariant, propB) + .isPermittedForVariantAndProperty(englishVariant, propB, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -220,7 +220,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(statePropAInv); manager - .isPermittedForVariantAndProperty(invariantVariant, propA) + .isPermittedForVariantAndProperty(invariantVariant, propA, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); @@ -234,7 +234,7 @@ describe('UmbVariantPropertyGuardManager', () => { manager.addRule(stateNoPropAInv); manager - .isPermittedForVariantAndProperty(invariantVariant, propA) + .isPermittedForVariantAndProperty(invariantVariant, propA, invariantVariant) .subscribe((value) => { expect(value).to.be.false; done(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts index fb2612f537..49a0a4c922 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-guard-manager/variant-property-guard.manager.ts @@ -5,21 +5,39 @@ import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; import { UmbGuardManagerBase } from '@umbraco-cms/backoffice/utils'; export interface UmbVariantPropertyGuardRule extends UmbPropertyGuardRule { + /** + * @description - The variant id of the property. + * @type {UmbVariantId} + * @memberof UmbVariantPropertyGuardRule + */ variantId?: UmbVariantId; + + /** + * @description - The variant id of the dataset. This is used to determine if the rule applies to the current dataset. + * @type {UmbVariantId} + * @memberof UmbVariantPropertyGuardRule + */ + datasetVariantId?: UmbVariantId; } /** * - * @param rule - * @param variantId - * @param propertyType + * @param {UmbVariantPropertyGuardRule} rule - The rule to check. + * @param {UmbVariantId} variantId - The property variant id to check. + * @param {UmbReferenceByUnique} propertyType - The property type to check. + * @param {UmbVariantId} datasetVariantId - The variant id of the dataset. This is used to determine if the rule applies to the current dataset. + * @returns {boolean} - Returns true if the rule applies to the given conditions. */ -function findRule(rule: UmbVariantPropertyGuardRule, variantId: UmbVariantId, propertyType: UmbReferenceByUnique) { +function findRule( + rule: UmbVariantPropertyGuardRule, + variantId: UmbVariantId, + propertyType: UmbReferenceByUnique, + datasetVariantId: UmbVariantId, +) { return ( - (rule.variantId?.compare(variantId) && rule.propertyType?.unique === propertyType.unique) || - (rule.variantId === undefined && rule.propertyType?.unique === propertyType.unique) || - (rule.variantId?.compare(variantId) && rule.propertyType === undefined) || - (rule.variantId === undefined && rule.propertyType === undefined) + (rule.variantId === undefined || rule.variantId.culture === variantId.culture) && + (rule.propertyType === undefined || rule.propertyType.unique === propertyType.unique) && + (rule.datasetVariantId === undefined || rule.datasetVariantId.culture === datasetVariantId.culture) ); } @@ -34,33 +52,54 @@ export class UmbVariantPropertyGuardManager extends UmbGuardManagerBase} - Returns an observable that emits true if the variant and propertyType is permitted, false otherwise. * @memberof UmbVariantPropertyGuardManager */ - isPermittedForVariantAndProperty(variantId: UmbVariantId, propertyType: UmbReferenceByUnique): Observable { - return this._rules.asObservablePart((rules) => this.#resolvePermission(rules, variantId, propertyType)); + isPermittedForVariantAndProperty( + variantId: UmbVariantId, + propertyType: UmbReferenceByUnique, + datasetVariantId: UmbVariantId, + ): Observable { + return this._rules.asObservablePart((rules) => + this.#resolvePermission(rules, variantId, propertyType, datasetVariantId), + ); } /** * Checks if the variant and propertyType is permitted. * @param {UmbVariantId} variantId - The variant id to check. * @param {UmbReferenceByUnique} propertyType - The property type to check. + * @param {UmbVariantId} datasetVariantId - The dataset variant id to check. * @returns {boolean} - Returns true if the variant and propertyType is permitted, false otherwise. * @memberof UmbVariantPropertyGuardManager */ - getIsPermittedForVariantAndProperty(variantId: UmbVariantId, propertyType: UmbReferenceByUnique): boolean { - return this.#resolvePermission(this._rules.getValue(), variantId, propertyType); + getIsPermittedForVariantAndProperty( + variantId: UmbVariantId, + propertyType: UmbReferenceByUnique, + datasetVariantId: UmbVariantId, + ): boolean { + return this.#resolvePermission(this._rules.getValue(), variantId, propertyType, datasetVariantId); } #resolvePermission( rules: UmbVariantPropertyGuardRule[], variantId: UmbVariantId, propertyType: UmbReferenceByUnique, + datasetVariantId: UmbVariantId, ) { - if (rules.filter((x) => x.permitted === false).some((rule) => findRule(rule, variantId, propertyType))) { + if ( + rules + .filter((x) => x.permitted === false) + .some((rule) => findRule(rule, variantId, propertyType, datasetVariantId)) + ) { return false; } - if (rules.filter((x) => x.permitted === true).some((rule) => findRule(rule, variantId, propertyType))) { + if ( + rules + .filter((x) => x.permitted === true) + .some((rule) => findRule(rule, variantId, propertyType, datasetVariantId)) + ) { return true; } return this._fallback; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts index 52f782f3a6..66080404f6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts @@ -1,71 +1,15 @@ import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentWorkspaceContext } from '../types.js'; -import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../global-contexts/index.js'; import { UmbContentPropertyDatasetContext } from '@umbraco-cms/backoffice/content'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { DocumentConfigurationResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; -import type { UmbVariantPropertyGuardRule } from '@umbraco-cms/backoffice/property'; export class UmbDocumentPropertyDatasetContext extends UmbContentPropertyDatasetContext< UmbDocumentDetailModel, UmbDocumentTypeDetailModel, UmbDocumentVariantModel > { - #dataSetVariantId?: UmbVariantId; - #documentConfiguration?: DocumentConfigurationResponseModel; - #allowEditInvariantFromNonDefault: boolean | undefined = undefined; - constructor(host: UmbControllerHost, dataOwner: UmbDocumentWorkspaceContext, variantId?: UmbVariantId) { super(host, dataOwner, variantId); - - this.#dataSetVariantId = variantId; - - this.consumeContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT, async (context) => { - if (!context) return; - this.#documentConfiguration = (await context?.getDocumentConfiguration()) ?? undefined; - this.#allowEditInvariantFromNonDefault = this.#documentConfiguration?.allowEditInvariantFromNonDefault; - if (this.#allowEditInvariantFromNonDefault === false) { - this.#preventEditInvariantFromNonDefault(); - } - }); - } - - #preventEditInvariantFromNonDefault() { - this.observe( - observeMultiple([this._dataOwner.structure.contentTypeProperties, this._dataOwner.variantOptions]), - ([properties, variantOptions]) => { - if (properties.length === 0) return; - if (variantOptions.length === 0) return; - - const currentVariantOption = variantOptions.find( - (option) => option.culture === this.#dataSetVariantId?.culture, - ); - - const isDefaultLanguage = currentVariantOption?.language.isDefault; - - properties.forEach((property) => { - const unique = 'UMB_PREVENT_EDIT_INVARIANT_FROM_NON_DEFAULT_' + property.unique; - - // Clean up the rule if it exists before adding a new one - this._dataOwner.propertyWriteGuard.removeRule(unique); - - // If the property is invariant and not in the default language, we need to add a rule - if (!property.variesByCulture && !isDefaultLanguage) { - const rule: UmbVariantPropertyGuardRule = { - unique, - message: 'Shared properties can only be edited in the default language', - propertyType: { - unique: property.unique, - }, - permitted: false, - }; - - this._dataOwner.propertyWriteGuard.addRule(rule); - } - }); - }, - ); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index b783e0a17c..4df13fc701 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -38,6 +38,8 @@ import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; import { ensurePathEndsWithSlash, UmbDeprecation } from '@umbraco-cms/backoffice/utils'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbVariantPropertyGuardRule } from '@umbraco-cms/backoffice/property'; type ContentModel = UmbDocumentDetailModel; type ContentTypeModel = UmbDocumentTypeDetailModel; @@ -89,6 +91,7 @@ export class UmbDocumentWorkspaceContext this.consumeContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT, async (context) => { const config = await context?.getDocumentConfiguration(); const allowSegmentCreation = config?.allowNonExistingSegmentsCreation ?? false; + const allowEditInvariantFromNonDefault = config?.allowEditInvariantFromNonDefault ?? true; this._variantOptionsFilter = (variantOption) => { const isNotCreatedSegmentVariant = variantOption.segment && !variantOption.variant; @@ -100,6 +103,10 @@ export class UmbDocumentWorkspaceContext return true; }; + + if (allowEditInvariantFromNonDefault === false) { + this.#preventEditInvariantFromNonDefault(); + } }); this.observe( @@ -428,6 +435,36 @@ export class UmbDocumentWorkspaceContext message, }); } + + #preventEditInvariantFromNonDefault() { + this.observe( + observeMultiple([this.structure.contentTypeProperties, this.variantOptions]), + ([properties, variantOptions]) => { + if (properties.length === 0) return; + if (variantOptions.length === 0) return; + + variantOptions.forEach((variantOption) => { + // Do not add a rule for the default language. It is always permitted to edit. + if (variantOption.language.isDefault) return; + + const datasetVariantId = UmbVariantId.CreateFromPartial(variantOption); + const invariantVariantId = UmbVariantId.CreateInvariant(); + const unique = `UMB_PREVENT_EDIT_INVARIANT_FROM_NON_DEFAULT_DATASET=${datasetVariantId.toString()}_PROPERTY_${invariantVariantId.toString()}`; + + const rule: UmbVariantPropertyGuardRule = { + unique, + message: 'Shared properties can only be edited in the default language', + variantId: invariantVariantId, + datasetVariantId, + permitted: false, + }; + + this.propertyWriteGuard.addRule(rule); + console.log('Adding rule', rule); + }); + }, + ); + } } export default UmbDocumentWorkspaceContext;