diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a7b36adf1..2b8d19b89 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -63,6 +63,7 @@ import { VersionManagerDialogAdapterComponent } from './components/versions-dial import { BrowsingFilesService } from './common/services/browsing-files.service'; import { ContentManagementService } from './common/services/content-management.service'; import { NodeActionsService } from './common/services/node-actions.service'; +import { NodePermissionService } from './common/services/node-permission.service'; import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material'; import { SearchComponent } from './components/search/search.component'; @@ -125,7 +126,8 @@ import { SearchComponent } from './components/search/search.component'; }, BrowsingFilesService, ContentManagementService, - NodeActionsService + NodeActionsService, + NodePermissionService ], entryComponents: [ VersionManagerDialogAdapterComponent diff --git a/src/app/common/services/node-permission.service.spec.ts b/src/app/common/services/node-permission.service.spec.ts new file mode 100644 index 000000000..50752ae6b --- /dev/null +++ b/src/app/common/services/node-permission.service.spec.ts @@ -0,0 +1,190 @@ +/*! + * @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 { NodePermissionService } from './node-permission.service'; + +describe('NodePermissionService', () => { + let permission: NodePermissionService; + + beforeEach(() => { + permission = new NodePermissionService(); + }); + + + it('should return false when source is null', () => { + const source = null; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + describe('Multiple source permission', () => { + it('should return true when source has allowableOperations permission', () => { + const source = [ + { entry: { allowableOperations: ['update'] } }, + { entry: { allowableOperations: ['update'] } }, + { entry: { allowableOperations: ['update'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(true); + }); + + it('should return true when source has allowableOperationsOnTarget permission', () => { + const source = [ + { entry: { allowableOperationsOnTarget: ['update'] } }, + { entry: { allowableOperationsOnTarget: ['update'] } }, + { entry: { allowableOperationsOnTarget: ['update'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(true); + }); + + it('should return false when source does not have allowableOperations permission', () => { + const source = [ + { entry: { allowableOperations: ['update'] } }, + { entry: { allowableOperations: ['update'] } }, + { entry: { allowableOperations: ['delete'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return false when source does not have allowableOperationsOnTarget permission', () => { + const source = [ + { entry: { allowableOperationsOnTarget: ['update'] } }, + { entry: { allowableOperationsOnTarget: ['update'] } }, + { entry: { allowableOperationsOnTarget: ['delete'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return true when source has `OR` allowableOperations permission', () => { + const source = [ + { entry: { allowableOperations: ['update' , 'delete'] } }, + { entry: { allowableOperations: ['update', 'create'] } }, + { entry: { allowableOperations: ['update', 'updatePermissions'] } } + ]; + + expect(permission.check(source, ['update', 'create'])).toBe(true); + }); + + it('should return true when source has `AND` allowableOperations permission', () => { + const source = [ + { entry: { allowableOperations: ['update' , 'delete', 'other'] } }, + { entry: { allowableOperations: ['update', 'create', 'other'] } }, + { entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } } + ]; + + expect(permission.check(source, ['update', 'other'], 'AND')).toBe(true); + }); + + it('should return false when source has no `AND` allowableOperations permission', () => { + const source = [ + { entry: { allowableOperations: ['update' , 'delete', 'other'] } }, + { entry: { allowableOperations: ['update', 'create', 'other'] } }, + { entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } } + ]; + + expect(permission.check(source, ['update', 'bogus'], 'AND')).toBe(false); + }); + + it('should return false when source has no allowableOperations', () => { + const source = [ + { entry: { allowableOperations: [] } }, + { entry: { allowableOperations: [] } }, + { entry: { allowableOperations: ['update'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return false when source has no allowableOperations property', () => { + const source = [ + { entry: { } }, + { entry: { } }, + { entry: { allowableOperations: ['update'] } } + ]; + + expect(permission.check(source, ['update'])).toBe(false); + }); + }); + + + describe('Single source permission', () => { + it('should return true when source has allowableOperations permission', () => { + const source = { entry: { allowableOperations: ['update'] } }; + + expect(permission.check(source, ['update'])).toBe(true); + }); + + it('should return true when source has allowableOperationsOnTarget permission', () => { + const source = { entry: { allowableOperationsOnTarget: ['update'] } }; + + expect(permission.check(source, ['update'])).toBe(true); + }); + + it('should return false when source does not have allowableOperations permission', () => { + const source = { entry: { allowableOperations: ['delete'] } }; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return false when source does not have allowableOperationsOnTarget permission', () => { + const source = { entry: { allowableOperationsOnTarget: ['delete'] } }; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return true when source has `OR` allowableOperations permission', () => { + const source = { entry: { allowableOperations: ['update'] } }; + + expect(permission.check(source, ['update', 'create'])).toBe(true); + }); + + it('should return true when source has `AND` allowableOperations permission', () => { + const source = { entry: { allowableOperations: ['update', 'other'] } }; + + expect(permission.check(source, ['update', 'other'], 'AND')).toBe(true); + }); + + it('should return false when source has no `AND` allowableOperations permission', () => { + const source = { entry: { allowableOperations: ['update', 'updatePermissions', 'other'] } }; + + expect(permission.check(source, ['update', 'bogus'], 'AND')).toBe(false); + }); + + it('should return false when source has no allowableOperations', () => { + const source = { entry: { allowableOperations: [] } }; + + expect(permission.check(source, ['update'])).toBe(false); + }); + + it('should return false when source has no allowableOperations property', () => { + const source = { entry: { } }; + + expect(permission.check(source, ['update'])).toBe(false); + }); + }); +}); diff --git a/src/app/common/services/node-permission.service.ts b/src/app/common/services/node-permission.service.ts new file mode 100644 index 000000000..9c8089745 --- /dev/null +++ b/src/app/common/services/node-permission.service.ts @@ -0,0 +1,77 @@ +/*! + * @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 { Injectable } from '@angular/core'; + +@Injectable() +export class NodePermissionService { + static DEFAULT_OPERATION = 'OR'; + + check(source: any, permissions: string[], operation: string = NodePermissionService.DEFAULT_OPERATION): boolean { + if (source) { + if (Array.isArray(source) && source.length) { + const arr = this.sanitize(source); + + return !!arr.length && source.every(node => this.hasPermission(node, permissions, operation)); + } + + return this.hasPermission(source, permissions, operation); + } + + return false; + } + + private hasPermission(node, permissions, operation): boolean { + const allowableOperations = this.getAllowableOperations(node); + + if (allowableOperations.length) { + if (operation === NodePermissionService.DEFAULT_OPERATION) { + return permissions.some(permission => allowableOperations.includes(permission)); + } else { + return permissions.every(permission => allowableOperations.includes(permission)); + } + } + + return false; + } + + private getAllowableOperations(node): string[] { + const entry = node.entry || node; + + if (entry.allowableOperationsOnTarget) { + return entry.allowableOperationsOnTarget; + } + + if (entry.allowableOperations) { + return entry.allowableOperations; + } + + return []; + } + + private sanitize(selection): any[] { + return (selection || []).filter(item => item); + } +} diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 346c29a3f..87b732839 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -1,7 +1,23 @@ + + + + + + + + + + - + {{ selection.hasFavorites() ? 'star' :'star_border' }} {{ 'APP.ACTIONS.FAVORITE' | translate }} @@ -23,33 +42,32 @@ diff --git a/src/app/components/preview/preview.component.spec.ts b/src/app/components/preview/preview.component.spec.ts index c52b49ee1..bbd5dfb88 100644 --- a/src/app/components/preview/preview.component.spec.ts +++ b/src/app/components/preview/preview.component.spec.ts @@ -36,7 +36,7 @@ import { HttpClientModule } from '@angular/common/http'; import { PreviewComponent } from './preview.component'; import { Observable } from 'rxjs/Rx'; -import { ContentManagementService } from '../../common/services/content-management.service'; +import { NodePermissionService } from '../../common/services/node-permission.service'; import { MatSnackBarModule } from '@angular/material'; describe('PreviewComponent', () => { @@ -64,7 +64,7 @@ describe('PreviewComponent', () => { CookieService, NotificationService, UserPreferencesService, - ContentManagementService + NodePermissionService ], declarations: [ PreviewComponent, diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 20cda82e4..adc97d31e 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -27,7 +27,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AlfrescoApiService, UserPreferencesService, ObjectUtils } from '@alfresco/adf-core'; import { Node, MinimalNodeEntity } from 'alfresco-js-api'; -import { ContentManagementService } from '../../common/services/content-management.service'; +import { NodePermissionService } from '../../common/services/node-permission.service'; @Component({ selector: 'app-preview', @@ -54,10 +54,10 @@ export class PreviewComponent implements OnInit { selectedEntities: MinimalNodeEntity[] = []; constructor(private router: Router, - private route: ActivatedRoute, - private apiService: AlfrescoApiService, - private preferences: UserPreferencesService, - private content: ContentManagementService) { + private route: ActivatedRoute, + private apiService: AlfrescoApiService, + private preferences: UserPreferencesService, + public permission: NodePermissionService) { } ngOnInit() { @@ -324,28 +324,11 @@ export class PreviewComponent implements OnInit { return path; } - canDeleteFile(): boolean { - return this.content.canDeleteNode(this.node); - } - async deleteFile() { try { - await this.content.deleteNode(this.node); + await this.permission.check(this.node, ['delete']); this.onVisibilityChanged(false); } catch { } } - - canMoveFile(): boolean { - return this.content.canMoveNode(this.node); - } - - canCopyFile(): boolean { - return this.content.canCopyNode(this.node); - } - - canManageVersions(): boolean { - return this.node.isFile && this.content.nodeHasPermission(this.node, 'update'); - } - } diff --git a/src/app/ui/application.scss b/src/app/ui/application.scss index 6500f96b6..d7122b7bf 100644 --- a/src/app/ui/application.scss +++ b/src/app/ui/application.scss @@ -27,5 +27,6 @@ ng-component { @import './overrides/alfresco-upload-button'; @import './overrides/alfresco-upload-dialog'; @import './overrides/toolbar'; +@import './overrides/adf-viewer-more-actions'; @import './overrides/breadcrumb'; @import './overrides/adf-info-drawer'; diff --git a/src/app/ui/custom-theme.scss b/src/app/ui/custom-theme.scss index d84615b8f..9cd3784d1 100644 --- a/src/app/ui/custom-theme.scss +++ b/src/app/ui/custom-theme.scss @@ -3,6 +3,7 @@ @import '../components/sidenav/sidenav.component.theme'; @import './overrides/toolbar'; +@import './overrides/adf-viewer-more-actions'; $grey-scale: ( 50 : #e0e0e0, @@ -47,4 +48,5 @@ $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent); @mixin custom-theme($theme) { @include sidenav-component-theme($custom-theme); @include toolbar-component-theme($custom-theme); + @include viewer-more-actions-component-theme($custom-theme); } diff --git a/src/app/ui/overrides/_adf-viewer-more-actions.scss b/src/app/ui/overrides/_adf-viewer-more-actions.scss new file mode 100644 index 000000000..fc9a7cc62 --- /dev/null +++ b/src/app/ui/overrides/_adf-viewer-more-actions.scss @@ -0,0 +1,18 @@ +@mixin viewer-more-actions-component-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $background: map-get($theme, background); + + .adf-viewer-more-actions { + @include angular-material-theme($theme); + + .toolbar__option--active { + color: mat-color($accent) !important; + } + + .toolbar__option--default { + color: mat-color($primary, .87) !important; + } + } + +}