diff --git a/docs/extending/rules.md b/docs/extending/rules.md index bfd37b236..68efe759b 100644 --- a/docs/extending/rules.md +++ b/docs/extending/rules.md @@ -129,29 +129,30 @@ The button will be visible only when the linked rule evaluates to `true`. ## Application Evaluators -| Key | Description | -| ------------------------------- | ------------------------------------------------------------ | -| app.selection.canDelete | User has permission to delete selected node(s). | -| app.selection.canDownload | User can download selected node(s). | -| app.selection.notEmpty | At least one node is selected. | -| app.selection.canUnshare | User is able to remove selected node(s) from public sharing. | -| app.selection.canAddFavorite | User can add selected node(s) to favorites. | -| app.selection.canRemoveFavorite | User can remove selected node(s) from favorites. | -| app.selection.first.canUpdate | User has permission to update selected node(s). | -| app.selection.file | A single File node is selected. | -| app.selection.file.canShare | User is able to share the selected file. | -| app.selection.file.isShared | A shared node is selected | -| app.selection.file.isLocked | File is locked for editing | -| app.selection.library | A single Library node is selected. | -| app.selection.isPrivateLibrary | A private Library node is selected. | -| app.selection.hasLibraryRole | The selected Library node has a role property. | -| app.selection.hasNoLibraryRole | The selected Library node has no role property. | -| app.selection.folder | A single Folder node is selected. | -| app.selection.folder.canUpdate | User has permissions to update the selected folder. | -| app.selection.folder.canUpdate | User has permissions to update the selected folder. | -| app.selection.file.canLock | User has permissions to lock file. | -| app.selection.file.canUnlock | User has permissions to unlock file. | -| repository.isQuickShareEnabled | Whether the quick share repository option is enabled or not. | +| Key | Description | +| ----------------------------------- | ------------------------------------------------------------ | +| app.selection.canDelete | User has permission to delete selected node(s). | +| app.selection.canDownload | User can download selected node(s). | +| app.selection.notEmpty | At least one node is selected. | +| app.selection.canUnshare | User is able to remove selected node(s) from public sharing. | +| app.selection.canAddFavorite | User can add selected node(s) to favorites. | +| app.selection.canRemoveFavorite | User can remove selected node(s) from favorites. | +| app.selection.first.canUpdate | User has permission to update selected node(s). | +| app.selection.file | A single File node is selected. | +| app.selection.file.canShare | User is able to share the selected file. | +| app.selection.file.isShared | A shared node is selected | +| app.selection.file.isLocked | File is locked for editing | +| app.selection.file.canUploadVersion | User can update file version | +| app.selection.library | A single Library node is selected. | +| app.selection.isPrivateLibrary | A private Library node is selected. | +| app.selection.hasLibraryRole | The selected Library node has a role property. | +| app.selection.hasNoLibraryRole | The selected Library node has no role property. | +| app.selection.folder | A single Folder node is selected. | +| app.selection.folder.canUpdate | User has permissions to update the selected folder. | +| app.selection.folder.canUpdate | User has permissions to update the selected folder. | +| app.selection.file.canLock | User has permissions to lock file. | +| app.selection.file.canUnlock | User has permissions to unlock file. | +| repository.isQuickShareEnabled | Whether the quick share repository option is enabled or not. | ## Navigation Evaluators diff --git a/src/app/extensions/core.extensions.module.ts b/src/app/extensions/core.extensions.module.ts index 0ac434f4d..4c4d3d771 100644 --- a/src/app/extensions/core.extensions.module.ts +++ b/src/app/extensions/core.extensions.module.ts @@ -123,6 +123,7 @@ export class CoreExtensionsModule { 'app.selection.file.canShare': app.canShareFile, 'app.selection.file.isShared': app.isShared, 'app.selection.file.isLocked': app.hasLockedFiles, + 'app.selection.file.canUploadVersion': app.canUploadVersion, 'app.selection.library': app.hasLibrarySelected, 'app.selection.isPrivateLibrary': app.isPrivateLibrary, 'app.selection.hasLibraryRole': app.hasLibraryRole, diff --git a/src/app/extensions/evaluators/app.evaluators.spec.ts b/src/app/extensions/evaluators/app.evaluators.spec.ts new file mode 100644 index 000000000..c565df995 --- /dev/null +++ b/src/app/extensions/evaluators/app.evaluators.spec.ts @@ -0,0 +1,316 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import * as app from './app.evaluators'; + +describe('app.evaluators', () => { + describe('isWriteLocked', () => { + it('should return [true] if lock type is set', () => { + const context: any = { + selection: { + file: { + entry: { + properties: { + 'cm:lockType': 'WRITE_LOCK' + } + } + } + } + }; + + expect(app.isWriteLocked(context, null)).toBe(true); + }); + + it('should return [false] if lock type is not set', () => { + const context: any = { + selection: { + file: { + entry: { + properties: {} + } + } + } + }; + + expect(app.isWriteLocked(context, null)).toBe(false); + }); + + it('should return [false] if selection not present', () => { + const context: any = {}; + + expect(app.isWriteLocked(context, null)).toBe(false); + }); + }); + + describe('hasLockedFiles', () => { + it('should return [false] if selection not present', () => { + const context: any = {}; + expect(app.hasLockedFiles(context, null)).toBe(false); + }); + + it('should return [false] if nodes not present', () => { + const context: any = { + selection: { + nodes: null + } + }; + + expect(app.hasLockedFiles(context, null)).toBe(false); + }); + + it('should return [false] if no files selected', () => { + const context: any = { + selection: { + nodes: [ + { + entry: { + isFile: false + } + }, + { + entry: { + isFile: false + } + } + ] + } + }; + + expect(app.hasLockedFiles(context, null)).toBe(false); + }); + + it('should return [true] when one of files is locked', () => { + const context: any = { + selection: { + nodes: [ + { + entry: { + isFile: true, + isLocked: true + } + }, + { + entry: { + isFile: true, + isLocked: false + } + } + ] + } + }; + + expect(app.hasLockedFiles(context, null)).toBe(true); + }); + }); + + it('should return [true] when one of files has readonly lock', () => { + const context: any = { + selection: { + nodes: [ + { + entry: { + isFile: true, + isLocked: false + } + }, + { + entry: { + isFile: true, + isLocked: false, + properties: { + 'cm:lockType': 'READ_ONLY_LOCK' + } + } + } + ] + } + }; + + expect(app.hasLockedFiles(context, null)).toBe(true); + }); + + describe('canUpdateSelectedNode', () => { + it('should return [false] if selection not preset', () => { + const context: any = {}; + + expect(app.canUpdateSelectedNode(context, null)).toBe(false); + }); + + it('should return [false] if selection is empty', () => { + const context: any = { + selection: { + isEmpty: true + } + }; + + expect(app.canUpdateSelectedNode(context, null)).toBe(false); + }); + + it('should return [false] if first selection is not a file', () => { + const context: any = { + permissions: { + check: () => false + }, + selection: { + isEmpty: false, + first: { + entry: { + isFile: false + } + } + } + }; + + expect(app.canUpdateSelectedNode(context, null)).toBe(false); + }); + + it('should return [false] if the file is locked', () => { + const context: any = { + permissions: { + check: () => true + }, + selection: { + isEmpty: false, + nodes: [ + { + entry: { + isFile: true, + isLocked: true + } + } + ], + first: { + entry: { + isFile: true, + isLocked: true + } + } + } + }; + + expect(app.canUpdateSelectedNode(context, null)).toBe(false); + }); + + it('should evaluate allowable operation for the file', () => { + const context: any = { + permissions: { + check: () => true + }, + selection: { + isEmpty: false, + nodes: [ + { + entry: { + isFile: true, + allowableOperationsOnTarget: [] + } + } + ], + first: { + entry: { + isFile: true, + allowableOperationsOnTarget: [] + } + } + } + }; + + expect(app.canUpdateSelectedNode(context, null)).toBe(true); + }); + }); + + describe('canUploadVersion', () => { + it('should return [true] if user has locked it previously', () => { + const context: any = { + profile: { + id: 'user1' + }, + selection: { + file: { + entry: { + properties: { + 'cm:lockType': 'WRITE_LOCK', + 'cm:lockOwner': { + id: 'user1' + } + } + } + } + } + }; + + expect(app.canUploadVersion(context, null)).toBe(true); + }); + + it('should return [false] if other user has locked it previously', () => { + const context: any = { + profile: { + id: 'user2' + }, + selection: { + file: { + entry: { + properties: { + 'cm:lockType': 'WRITE_LOCK', + 'cm:lockOwner': { + id: 'user1' + } + } + } + } + } + }; + + expect(app.canUploadVersion(context, null)).toBe(false); + }); + + it('should check the [update] operation when no write lock present', () => { + let checked = false; + const context: any = { + permissions: { + check: () => (checked = true) + }, + selection: { + isEmpty: false, + nodes: [ + { + entry: { + isFile: true + } + } + ], + first: { + entry: { + isFile: true + } + } + } + }; + + expect(app.canUploadVersion(context, null)).toBe(true); + expect(checked).toBe(true); + }); + }); +}); diff --git a/src/app/extensions/evaluators/app.evaluators.ts b/src/app/extensions/evaluators/app.evaluators.ts index 89a6f532b..19c5a5a55 100644 --- a/src/app/extensions/evaluators/app.evaluators.ts +++ b/src/app/extensions/evaluators/app.evaluators.ts @@ -283,17 +283,21 @@ export function hasLockedFiles( context: RuleContext, ...args: RuleParameter[] ): boolean { - return context.selection.nodes.some(node => { - if (!node.entry.isFile) { - return false; - } + if (context && context.selection && context.selection.nodes) { + return context.selection.nodes.some(node => { + if (!node.entry.isFile) { + return false; + } - return ( - node.entry.isLocked || - (node.entry.properties && - node.entry.properties['cm:lockType'] === 'READ_ONLY_LOCK') - ); - }); + return ( + node.entry.isLocked || + (node.entry.properties && + node.entry.properties['cm:lockType'] === 'READ_ONLY_LOCK') + ); + }); + } + + return false; } export function isWriteLocked( @@ -301,6 +305,8 @@ export function isWriteLocked( ...args: RuleParameter[] ): boolean { return !!( + context && + context.selection && context.selection.file && context.selection.file.entry && context.selection.file.entry.properties && @@ -340,3 +346,12 @@ export function canUnlockFile( isUserWriteLockOwner(context, ...args)) ); } + +export function canUploadVersion( + context: AppRuleContext, + ...args: RuleParameter[] +): boolean { + return isWriteLocked(context, ...args) + ? isUserWriteLockOwner(context, ...args) + : canUpdateSelectedNode(context, ...args); +} diff --git a/src/assets/app.extensions.json b/src/assets/app.extensions.json index b638879cc..a64f3b90f 100644 --- a/src/assets/app.extensions.json +++ b/src/assets/app.extensions.json @@ -662,7 +662,7 @@ "click": "UPLOAD_FILE_VERSION" }, "rules": { - "visible": "app.toolbar.versions" + "visible": "app.selection.file.canUploadVersion" } }, { @@ -878,7 +878,7 @@ "click": "UPLOAD_FILE_VERSION" }, "rules": { - "visible": "app.toolbar.versions" + "visible": "app.selection.file.canUploadVersion" } }, { @@ -1102,7 +1102,7 @@ "click": "UPLOAD_FILE_VERSION" }, "rules": { - "visible": "app.toolbar.versions" + "visible": "app.selection.file.canUploadVersion" } }, {