Skip to content

Commit 1e50714

Browse files
Set document to readonly when a user is not allowed to create / update (#18076)
* split manifest file * use const * use const * use const * require permission to create or update to render save action * require permission to update to render "save and preview" * require permission to update + publish to render "save and publish" * keep button as it is extendable * keep buttons as they are extendable * set readonly state based on user permission * render readonly tag for invariant documents * remove double array * return permitted state in callback * disable save button if all variants are read only * rename private method * split check on create vs update * clear state when resetting the state * add null check * remove unused * Update document-user-permission.condition.ts * change translation to browse * remove create permission
1 parent 469fd14 commit 1e50714

File tree

12 files changed

+203
-90
lines changed

12 files changed

+203
-90
lines changed

src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default {
1414
actions: {
1515
assigndomain: 'Culture and Hostnames',
1616
auditTrail: 'Audit Trail',
17-
browse: 'Browse Node',
17+
browse: 'Browse',
1818
changeDataType: 'Change Data Type',
1919
changeDocType: 'Change Document Type',
2020
chooseWhereToCopy: 'Choose where to copy',

src/Umbraco.Web.UI.Client/src/assets/lang/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default {
1313
actions: {
1414
assigndomain: 'Culture and Hostnames',
1515
auditTrail: 'Audit Trail',
16-
browse: 'Browse Node',
16+
browse: 'Browse',
1717
changeDataType: 'Change Data Type',
1818
changeDocType: 'Change Document Type',
1919
chooseWhereToCopy: 'Choose where to copy',

src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,11 @@ export abstract class UmbContentDetailWorkspaceContextBase<
719719
this._closeModal();
720720
}
721721

722+
override resetState() {
723+
super.resetState();
724+
this.readOnlyState.clear();
725+
}
726+
722727
abstract getContentTypeUnique(): string | undefined;
723728

724729
abstract createPropertyDatasetContext(

src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
4848
private _variantSelectorOpen = false;
4949

5050
@state()
51-
private _readOnlyCultures: string[] = [];
51+
private _readOnlyCultures: Array<string | null> = [];
5252

5353
// eslint-disable-next-line @typescript-eslint/no-unused-vars
5454
protected _variantSorter = (a: VariantOptionModelType, b: VariantOptionModelType) => {
@@ -181,8 +181,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
181181
#setReadOnlyCultures() {
182182
this._readOnlyCultures = this._variantOptions
183183
.filter((variant) => this._readOnlyStates.some((state) => state.variantId.compare(variant)))
184-
.map((variant) => variant.culture)
185-
.filter((item) => item !== null) as string[];
184+
.map((variant) => variant.culture);
186185
}
187186

188187
#onPopoverToggle(event: ToggleEvent) {
@@ -252,7 +251,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
252251
`
253252
: ''}
254253
`
255-
: nothing}
254+
: html`<span id="read-only-tag" slot="append"> ${this.#renderReadOnlyTag(null)} </span>`}
256255
</uui-input>
257256
258257
${this.#hasVariants()
@@ -310,12 +309,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
310309
}
311310

312311
#isReadOnly(culture: string | null) {
313-
if (!culture) return false;
314312
return this._readOnlyCultures.includes(culture);
315313
}
316314

317315
#renderReadOnlyTag(culture?: string | null) {
318-
if (!culture) return nothing;
316+
if (culture === undefined) return nothing;
319317
return this.#isReadOnly(culture)
320318
? html`<uui-tag look="secondary">${this.localize.term('general_readOnly')}</uui-tag>`
321319
: nothing;
@@ -368,6 +366,13 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
368366
white-space: nowrap;
369367
}
370368
369+
#read-only-tag {
370+
white-space: nowrap;
371+
display: flex;
372+
align-items: center;
373+
justify-content: center;
374+
}
375+
371376
uui-scroll-container {
372377
max-height: 50dvh;
373378
}

src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/workspace-action/manifests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
23
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
34
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
45
} from '../../../user-permissions/constants.js';
@@ -20,7 +21,7 @@ export const manifests: Array<UmbExtensionManifest> = [
2021
},
2122
conditions: [
2223
{
23-
alias: 'Umb.Condition.UserPermission.Document',
24+
alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
2425
allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH],
2526
},
2627
{

src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/workspace-action/manifests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
23
UMB_USER_PERMISSION_DOCUMENT_PUBLISH,
34
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
45
} from '../../../user-permissions/constants.js';
@@ -19,7 +20,7 @@ export const manifests: Array<UmbExtensionManifest> = [
1920
},
2021
conditions: [
2122
{
22-
alias: 'Umb.Condition.UserPermission.Document',
23+
alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
2324
allOf: [UMB_USER_PERMISSION_DOCUMENT_UPDATE, UMB_USER_PERMISSION_DOCUMENT_PUBLISH],
2425
},
2526
{

src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/workspace-action/manifests.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH } from '../../../user-permissions/constants.js';
1+
import {
2+
UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
3+
UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH,
4+
} from '../../../user-permissions/constants.js';
25
import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin';
36

47
export const manifests: Array<UmbExtensionManifest> = [
@@ -16,7 +19,7 @@ export const manifests: Array<UmbExtensionManifest> = [
1619
},
1720
conditions: [
1821
{
19-
alias: 'Umb.Condition.UserPermission.Document',
22+
alias: UMB_DOCUMENT_USER_PERMISSION_CONDITION_ALIAS,
2023
allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH],
2124
},
2225
{

src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/conditions/document-user-permission.condition.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,31 @@ import type { UmbDocumentUserPermissionConditionConfig } from './types.js';
33
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
44
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
55
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
6-
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
76
import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';
87
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
98
import type { DocumentPermissionPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
9+
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
10+
11+
// Do not export - for internal use only
12+
type UmbOnChangeCallbackType = (permitted: boolean) => void;
13+
14+
export class UmbDocumentUserPermissionCondition extends UmbControllerBase implements UmbExtensionCondition {
15+
config: UmbDocumentUserPermissionConditionConfig;
16+
permitted = false;
1017

11-
export class UmbDocumentUserPermissionCondition
12-
extends UmbConditionBase<UmbDocumentUserPermissionConditionConfig>
13-
implements UmbExtensionCondition
14-
{
1518
#entityType: string | undefined;
1619
#unique: string | null | undefined;
1720
#documentPermissions: Array<DocumentPermissionPresentationModel> = [];
1821
#fallbackPermissions: string[] = [];
22+
#onChange: UmbOnChangeCallbackType;
1923

2024
constructor(
2125
host: UmbControllerHost,
22-
args: UmbConditionControllerArguments<UmbDocumentUserPermissionConditionConfig>,
26+
args: UmbConditionControllerArguments<UmbDocumentUserPermissionConditionConfig, UmbOnChangeCallbackType>,
2327
) {
24-
super(host, args);
28+
super(host);
29+
this.config = args.config;
30+
this.#onChange = args.onChange;
2531

2632
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
2733
this.observe(
@@ -102,7 +108,11 @@ export class UmbDocumentUserPermissionCondition
102108
oneOfPermitted = false;
103109
}
104110

105-
this.permitted = allOfPermitted && oneOfPermitted;
111+
const permitted = allOfPermitted && oneOfPermitted;
112+
if (permitted === this.permitted) return;
113+
114+
this.permitted = permitted;
115+
this.#onChange(permitted);
106116
}
107117
}
108118

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../constants.js';
2+
import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin';
3+
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
4+
5+
export const manifests: Array<UmbExtensionManifest> = [
6+
{
7+
type: 'workspaceAction',
8+
kind: 'default',
9+
alias: 'Umb.WorkspaceAction.Document.Save',
10+
name: 'Save Document Workspace Action',
11+
weight: 80,
12+
api: () => import('./save.action.js'),
13+
meta: {
14+
label: '#buttons_save',
15+
look: 'secondary',
16+
color: 'positive',
17+
},
18+
conditions: [
19+
{
20+
alias: UMB_WORKSPACE_CONDITION_ALIAS,
21+
match: UMB_DOCUMENT_WORKSPACE_ALIAS,
22+
},
23+
{
24+
alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS,
25+
},
26+
],
27+
},
28+
{
29+
type: 'workspaceAction',
30+
kind: 'default',
31+
alias: 'Umb.WorkspaceAction.Document.SaveAndPreview',
32+
name: 'Save And Preview Document Workspace Action',
33+
weight: 90,
34+
api: () => import('./save-and-preview.action.js'),
35+
meta: {
36+
label: '#buttons_saveAndPreview',
37+
},
38+
conditions: [
39+
{
40+
alias: UMB_WORKSPACE_CONDITION_ALIAS,
41+
match: UMB_DOCUMENT_WORKSPACE_ALIAS,
42+
},
43+
{
44+
alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS,
45+
},
46+
],
47+
},
48+
];

src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,55 @@
1-
import {
2-
UMB_USER_PERMISSION_DOCUMENT_CREATE,
3-
UMB_USER_PERMISSION_DOCUMENT_UPDATE,
4-
} from '../../user-permissions/constants.js';
5-
import { UmbDocumentUserPermissionCondition } from '../../user-permissions/conditions/document-user-permission.condition.js';
1+
import type { UmbDocumentVariantModel } from '../../types.js';
2+
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-token.js';
3+
import type UmbDocumentWorkspaceContext from '../document-workspace.context.js';
4+
import type { UmbVariantState } from '@umbraco-cms/backoffice/utils';
65
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
76
import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
7+
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
88

99
export class UmbDocumentSaveWorkspaceAction extends UmbSubmitWorkspaceAction {
10+
#documentWorkspaceContext?: UmbDocumentWorkspaceContext;
11+
#variants: Array<UmbDocumentVariantModel> = [];
12+
#readOnlyStates: Array<UmbVariantState> = [];
13+
1014
constructor(host: UmbControllerHost, args: any) {
1115
super(host, args);
1216

13-
/* The action is disabled by default because the onChange callback
14-
will first be triggered when the condition is changed to permitted */
15-
this.disable();
16-
17-
// TODO: this check is not sufficient. It will show the save button if a use
18-
// has only create options. The best solution would be to split the two buttons into two separate actions
19-
// with a condition on isNew to show/hide them
20-
// The server will throw a permission error if this scenario happens
21-
const condition = new UmbDocumentUserPermissionCondition(host, {
22-
host,
23-
config: {
24-
alias: 'Umb.Condition.UserPermission.Document',
25-
oneOf: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_UPDATE],
17+
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
18+
this.#documentWorkspaceContext = context;
19+
this.#observeVariants();
20+
this.#observeReadOnlyStates();
21+
});
22+
}
23+
24+
#observeVariants() {
25+
this.observe(
26+
this.#documentWorkspaceContext?.variants,
27+
(variants) => {
28+
this.#variants = variants ?? [];
29+
this.#check();
2630
},
27-
onChange: () => {
28-
if (condition.permitted) {
29-
this.enable();
30-
} else {
31-
this.disable();
32-
}
31+
'saveWorkspaceActionVariantsObserver',
32+
);
33+
}
34+
35+
#observeReadOnlyStates() {
36+
this.observe(
37+
this.#documentWorkspaceContext?.readOnlyState.states,
38+
(readOnlyStates) => {
39+
this.#readOnlyStates = readOnlyStates ?? [];
40+
this.#check();
3341
},
42+
'saveWorkspaceActionReadOnlyStatesObserver',
43+
);
44+
}
45+
46+
#check() {
47+
const allVariantsAreReadOnly = this.#variants.every((variant) => {
48+
const variantId = new UmbVariantId(variant.culture, variant.segment);
49+
return this.#readOnlyStates.some((state) => state.variantId.equal(variantId));
3450
});
51+
52+
return allVariantsAreReadOnly ? this.disable() : this.enable();
3553
}
3654
}
3755

0 commit comments

Comments
 (0)