mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
extensibility: rules engine (#511)
* rules format prototype * config container * lightweight rules * fdescribe * basic rule integration * migrate "create folder" to click actions * migrate toolbar to new action handlers * rule support for "create folder" (toolbar) * upgrade "View" toolbar command * migrate to rules * cleanup tests
This commit is contained in:
committed by
Cilibiu Bogdan
parent
d5763f585d
commit
51af2071c2
@@ -42,6 +42,28 @@
|
|||||||
"plugin2.json"
|
"plugin2.json"
|
||||||
],
|
],
|
||||||
"core": {
|
"core": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"id": "app.create.canCreateFolder",
|
||||||
|
"type": "app.navigation.folder.canCreate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canEditFolder",
|
||||||
|
"type": "core.every",
|
||||||
|
"parameters": [
|
||||||
|
{ "type": "rule", "value": "app.selection.folder" },
|
||||||
|
{ "type": "rule", "value": "app.selection.folder.canUpdate" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canViewFile",
|
||||||
|
"type": "app.selection.file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "app.toolbar.canDownload",
|
||||||
|
"type": "app.selection.canDownload"
|
||||||
|
}
|
||||||
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"id": "aca:routes/about",
|
"id": "aca:routes/about",
|
||||||
@@ -81,11 +103,6 @@
|
|||||||
"type": "SNACKBAR_INFO",
|
"type": "SNACKBAR_INFO",
|
||||||
"payload": "I'm a nice little popup raised by extension."
|
"payload": "I'm a nice little popup raised by extension."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "aca:actions/error",
|
|
||||||
"type": "SNACKBAR_ERROR",
|
|
||||||
"payload": "Aw, Snap!"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "aca:actions/node-name",
|
"id": "aca:actions/node-name",
|
||||||
"type": "SNACKBAR_INFO",
|
"type": "SNACKBAR_INFO",
|
||||||
@@ -100,14 +117,14 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"create": [
|
"create": [
|
||||||
{
|
{
|
||||||
"disabled": false,
|
"id": "app.create.folder",
|
||||||
"id": "aca:create/folder",
|
|
||||||
"order": 100,
|
|
||||||
"icon": "create_new_folder",
|
"icon": "create_new_folder",
|
||||||
"title": "ext: Create Folder",
|
"title": "ext: Create Folder",
|
||||||
"target": {
|
"actions": {
|
||||||
"permissions": ["create"],
|
"click": "aca:actions/create-folder"
|
||||||
"action": "aca:actions/create-folder"
|
},
|
||||||
|
"rules": {
|
||||||
|
"enabled": "app.create.canCreateFolder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -210,10 +227,11 @@
|
|||||||
"order": 10,
|
"order": 10,
|
||||||
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||||
"icon": "create_new_folder",
|
"icon": "create_new_folder",
|
||||||
"target": {
|
"actions": {
|
||||||
"types": [],
|
"click": "aca:actions/create-folder"
|
||||||
"permissions": ["parent.create"],
|
},
|
||||||
"action": "aca:actions/create-folder"
|
"rules": {
|
||||||
|
"visible": "app.create.canCreateFolder"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -222,10 +240,11 @@
|
|||||||
"order": 15,
|
"order": 15,
|
||||||
"title": "APP.ACTIONS.VIEW",
|
"title": "APP.ACTIONS.VIEW",
|
||||||
"icon": "open_in_browser",
|
"icon": "open_in_browser",
|
||||||
"target": {
|
"actions": {
|
||||||
"types": ["file"],
|
"click": "aca:actions/preview"
|
||||||
"permissions": [],
|
},
|
||||||
"action": "aca:actions/preview"
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canViewFile"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -234,11 +253,11 @@
|
|||||||
"order": 20,
|
"order": 20,
|
||||||
"title": "APP.ACTIONS.DOWNLOAD",
|
"title": "APP.ACTIONS.DOWNLOAD",
|
||||||
"icon": "get_app",
|
"icon": "get_app",
|
||||||
"target": {
|
"actions": {
|
||||||
"types": ["file", "folder"],
|
"click": "aca:actions/download"
|
||||||
"permissions": [],
|
},
|
||||||
"action": "aca:actions/download",
|
"rules": {
|
||||||
"multiple": true
|
"visible": "app.toolbar.canDownload"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -247,10 +266,11 @@
|
|||||||
"order": 30,
|
"order": 30,
|
||||||
"title": "APP.ACTIONS.EDIT",
|
"title": "APP.ACTIONS.EDIT",
|
||||||
"icon": "create",
|
"icon": "create",
|
||||||
"target": {
|
"actions": {
|
||||||
"types": ["folder"],
|
"click": "aca:actions/edit-folder"
|
||||||
"permissions": ["update"],
|
},
|
||||||
"action": "aca:actions/edit-folder"
|
"rules": {
|
||||||
|
"visible": "app.toolbar.canEditFolder"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -270,21 +290,8 @@
|
|||||||
"type": "button",
|
"type": "button",
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"icon": "settings_applications",
|
"icon": "settings_applications",
|
||||||
"target": {
|
"actions": {
|
||||||
"types": [],
|
"click": "aca:actions/settings"
|
||||||
"permissions": [],
|
|
||||||
"action": "aca:actions/settings"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aca:action4",
|
|
||||||
"type": "button",
|
|
||||||
"title": "Error",
|
|
||||||
"icon": "report_problem",
|
|
||||||
"target": {
|
|
||||||
"types": ["file"],
|
|
||||||
"permissions": ["update", "delete"],
|
|
||||||
"action": "aca:actions/error"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -85,6 +85,7 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro
|
|||||||
import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-permissions.dialog';
|
import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-permissions.dialog';
|
||||||
import { NodePermissionsDirective } from './common/directives/node-permissions.directive';
|
import { NodePermissionsDirective } from './common/directives/node-permissions.directive';
|
||||||
import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component';
|
import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component';
|
||||||
|
import { RuleService } from './extensions/rules/rule.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -161,7 +162,8 @@ import { PermissionsManagerComponent } from './components/permission-manager/per
|
|||||||
ProfileResolver,
|
ProfileResolver,
|
||||||
ExperimentalGuard,
|
ExperimentalGuard,
|
||||||
ContentApiService,
|
ContentApiService,
|
||||||
ExtensionService
|
ExtensionService,
|
||||||
|
RuleService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
LibraryDialogComponent,
|
LibraryDialogComponent,
|
||||||
|
@@ -83,8 +83,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
if (selection.isEmpty) {
|
if (selection.isEmpty) {
|
||||||
this.infoDrawerOpened = false;
|
this.infoDrawerOpened = false;
|
||||||
}
|
}
|
||||||
const selectedNodes = selection ? selection.nodes : null;
|
this.actions = this.extensions.getAllowedContentActions();
|
||||||
this.actions = this.extensions.getAllowedContentActions(selectedNodes, this.node);
|
|
||||||
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
||||||
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
||||||
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
<button *ngFor="let entry of createActions"
|
<button *ngFor="let entry of createActions"
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="entry.disabled"
|
[disabled]="entry.disabled"
|
||||||
(click)="runAction(entry.target.action)">
|
(click)="runAction(entry.actions.click)">
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
<span>{{ entry.title | translate }}</span>
|
<span>{{ entry.title | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
title="{{ entry.title | translate }}"
|
title="{{ entry.title | translate }}"
|
||||||
(click)="runAction(entry.target.action)">
|
(click)="runAction(entry.actions.click)">
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar-divider *ngSwitchCase="'separator'"></adf-toolbar-divider>
|
<adf-toolbar-divider *ngSwitchCase="'separator'"></adf-toolbar-divider>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
[overlapTrigger]="false">
|
[overlapTrigger]="false">
|
||||||
<ng-container *ngFor="let child of entry.children">
|
<ng-container *ngFor="let child of entry.children">
|
||||||
<button mat-menu-item
|
<button mat-menu-item
|
||||||
(click)="runAction(child.target.action)">
|
(click)="runAction(child.actions.click)">
|
||||||
<mat-icon>{{ child.icon }}</mat-icon>
|
<mat-icon>{{ child.icon }}</mat-icon>
|
||||||
<span>{{ child.title | translate }}</span>
|
<span>{{ child.title | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -38,10 +38,13 @@ export interface ContentActionExtension {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
children?: Array<ContentActionExtension>;
|
children?: Array<ContentActionExtension>;
|
||||||
target: {
|
actions?: {
|
||||||
types: Array<string>;
|
click?: string;
|
||||||
permissions: Array<string>,
|
[key: string]: string;
|
||||||
action: string;
|
};
|
||||||
multiple?: boolean;
|
rules: {
|
||||||
|
enabled?: string;
|
||||||
|
visible?: string;
|
||||||
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
35
src/app/extensions/extension.config.ts
Normal file
35
src/app/extensions/extension.config.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RouteExtension } from './route.extension';
|
||||||
|
import { ActionExtension } from './action.extension';
|
||||||
|
import { RuleRef } from './rules/rule-ref';
|
||||||
|
|
||||||
|
export interface ExtensionConfig {
|
||||||
|
rules?: Array<RuleRef>;
|
||||||
|
routes?: Array<RouteExtension>;
|
||||||
|
actions?: Array<ActionExtension>;
|
||||||
|
features?: { [key: string]: any };
|
||||||
|
}
|
@@ -491,63 +491,6 @@ describe('ExtensionService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('permissions', () => {
|
|
||||||
it('should approve node permission', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: ['create']
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(extensions.nodeHasPermission(node, 'create')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not approve node permission', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: ['create']
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not approve node permission when node missing property', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: null
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require node to check permission', () => {
|
|
||||||
expect(extensions.nodeHasPermission(null, 'create')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require permission value to check', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: ['create']
|
|
||||||
};
|
|
||||||
expect(extensions.nodeHasPermission(node, null)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should approve multiple permissions', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: ['create', 'update', 'delete']
|
|
||||||
};
|
|
||||||
expect(
|
|
||||||
extensions.nodeHasPermissions(node, ['create', 'delete'])
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require node to check multiple permissions', () => {
|
|
||||||
expect(extensions.nodeHasPermissions(null, ['create'])).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require multiple permissions to check', () => {
|
|
||||||
const node: any = {
|
|
||||||
allowableOperations: ['create', 'update', 'delete']
|
|
||||||
};
|
|
||||||
expect(extensions.nodeHasPermissions(node, null)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sorting', () => {
|
describe('sorting', () => {
|
||||||
it('should sort by provided order', () => {
|
it('should sort by provided order', () => {
|
||||||
const sorted = [
|
const sorted = [
|
||||||
|
@@ -36,7 +36,8 @@ import { AppStore } from '../store/states';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { NavigationExtension } from './navigation.extension';
|
import { NavigationExtension } from './navigation.extension';
|
||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
import { Node, MinimalNodeEntity } from 'alfresco-js-api';
|
import { Node } from 'alfresco-js-api';
|
||||||
|
import { RuleService } from './rules/rule.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExtensionService {
|
export class ExtensionService {
|
||||||
@@ -52,7 +53,8 @@ export class ExtensionService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private config: AppConfigService,
|
private config: AppConfigService,
|
||||||
private store: Store<AppStore>
|
private store: Store<AppStore>,
|
||||||
|
private ruleService: RuleService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// initialise extension service
|
// initialise extension service
|
||||||
@@ -89,6 +91,8 @@ export class ExtensionService {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
.sort(this.sortByOrder);
|
.sort(this.sortByOrder);
|
||||||
|
|
||||||
|
this.ruleService.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRouteById(id: string): RouteExtension {
|
getRouteById(id: string): RouteExtension {
|
||||||
@@ -183,38 +187,28 @@ export class ExtensionService {
|
|||||||
|
|
||||||
// evaluates create actions for the folder node
|
// evaluates create actions for the folder node
|
||||||
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
||||||
return this.createActions.filter(this.filterEnabled).map(action => {
|
return this.createActions
|
||||||
if (
|
.filter(this.filterEnabled)
|
||||||
action.target &&
|
.filter(action => this.filterByRules(action))
|
||||||
action.target.permissions &&
|
.map(action => {
|
||||||
action.target.permissions.length > 0
|
let disabled = false;
|
||||||
) {
|
|
||||||
|
if (action.rules && action.rules.enabled) {
|
||||||
|
disabled = !this.ruleService.evaluateRule(action.rules.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
disabled: !this.nodeHasPermissions(
|
disabled
|
||||||
folder,
|
|
||||||
action.target.permissions
|
|
||||||
),
|
|
||||||
target: {
|
|
||||||
...action.target
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return action;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluates content actions for the selection and parent folder node
|
// evaluates content actions for the selection and parent folder node
|
||||||
getAllowedContentActions(
|
getAllowedContentActions(): Array<ContentActionExtension> {
|
||||||
nodes: MinimalNodeEntity[],
|
|
||||||
parentNode: Node
|
|
||||||
): Array<ContentActionExtension> {
|
|
||||||
return this.contentActions
|
return this.contentActions
|
||||||
.filter(this.filterEnabled)
|
.filter(this.filterEnabled)
|
||||||
.filter(action => this.filterByTarget(nodes, action))
|
.filter(action => this.filterByRules(action))
|
||||||
.filter(action =>
|
|
||||||
this.filterByPermission(nodes, action, parentNode)
|
|
||||||
)
|
|
||||||
.reduce(this.reduceSeparators, [])
|
.reduce(this.reduceSeparators, [])
|
||||||
.map(action => {
|
.map(action => {
|
||||||
if (action.type === ContentActionType.menu) {
|
if (action.type === ContentActionType.menu) {
|
||||||
@@ -222,14 +216,7 @@ export class ExtensionService {
|
|||||||
if (copy.children && copy.children.length > 0) {
|
if (copy.children && copy.children.length > 0) {
|
||||||
copy.children = copy.children
|
copy.children = copy.children
|
||||||
.filter(childAction =>
|
.filter(childAction =>
|
||||||
this.filterByTarget(nodes, childAction)
|
this.filterByRules(childAction)
|
||||||
)
|
|
||||||
.filter(childAction =>
|
|
||||||
this.filterByPermission(
|
|
||||||
nodes,
|
|
||||||
childAction,
|
|
||||||
parentNode
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.reduce(this.reduceSeparators, []);
|
.reduce(this.reduceSeparators, []);
|
||||||
}
|
}
|
||||||
@@ -301,108 +288,10 @@ export class ExtensionService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
filterByTarget(
|
filterByRules(action: ContentActionExtension): boolean {
|
||||||
nodes: MinimalNodeEntity[],
|
if (action && action.rules && action.rules.visible) {
|
||||||
action: ContentActionExtension
|
return this.ruleService.evaluateRule(action.rules.visible);
|
||||||
): boolean {
|
|
||||||
if (!action) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
if (!action.target) {
|
|
||||||
return (
|
|
||||||
action.type === ContentActionType.separator ||
|
|
||||||
action.type === ContentActionType.menu
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const types = action.target.types || [];
|
|
||||||
|
|
||||||
if (types.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes && nodes.length > 0) {
|
|
||||||
return types.some(type => {
|
|
||||||
if (type === 'folder') {
|
|
||||||
return action.target.multiple
|
|
||||||
? nodes.some(node => node.entry.isFolder)
|
|
||||||
: nodes.length === 1 &&
|
|
||||||
nodes.every(node => node.entry.isFolder);
|
|
||||||
}
|
|
||||||
if (type === 'file') {
|
|
||||||
return action.target.multiple
|
|
||||||
? nodes.some(node => node.entry.isFile)
|
|
||||||
: nodes.length === 1 &&
|
|
||||||
nodes.every(node => node.entry.isFile);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
filterByPermission(
|
|
||||||
nodes: MinimalNodeEntity[],
|
|
||||||
action: ContentActionExtension,
|
|
||||||
parentNode: Node
|
|
||||||
): boolean {
|
|
||||||
if (!action) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!action.target) {
|
|
||||||
return (
|
|
||||||
action.type === ContentActionType.separator ||
|
|
||||||
action.type === ContentActionType.menu
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const permissions = action.target.permissions || [];
|
|
||||||
|
|
||||||
if (permissions.length === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions.some(permission => {
|
|
||||||
if (permission.startsWith('parent.')) {
|
|
||||||
if (parentNode) {
|
|
||||||
const parentQuery = permission.split('.')[1];
|
|
||||||
return this.nodeHasPermission(parentNode, parentQuery);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes && nodes.length > 0) {
|
|
||||||
return action.target.multiple
|
|
||||||
? nodes.some(node =>
|
|
||||||
this.nodeHasPermission(node.entry, permission)
|
|
||||||
)
|
|
||||||
: nodes.length === 1 &&
|
|
||||||
nodes.every(node =>
|
|
||||||
this.nodeHasPermission(node.entry, permission)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeHasPermissions(node: Node, permissions: string[]): boolean {
|
|
||||||
if (node && permissions && permissions.length > 0) {
|
|
||||||
return permissions.some(permission =>
|
|
||||||
this.nodeHasPermission(node, permission)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeHasPermission(node: Node, permission: string): boolean {
|
|
||||||
if (node && permission) {
|
|
||||||
const allowableOperations = node.allowableOperations || [];
|
|
||||||
return allowableOperations.includes(permission);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
72
src/app/extensions/rules/app.evaluators.ts
Normal file
72
src/app/extensions/rules/app.evaluators.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RuleContext } from './rule-context';
|
||||||
|
import { RuleParameter } from './rule-parameter';
|
||||||
|
import { Node } from 'alfresco-js-api';
|
||||||
|
|
||||||
|
export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
const folder = context.navigation.currentFolder;
|
||||||
|
if (folder) {
|
||||||
|
return nodeHasPermission(folder, 'create');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canDownloadSelection(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
if (!context.selection.isEmpty) {
|
||||||
|
return context.selection.nodes.every(node => {
|
||||||
|
return node.entry && (node.entry.isFile || node.entry.isFolder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasFolderSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
const folder = context.selection.folder;
|
||||||
|
return folder ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasFileSelected(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
const file = context.selection.file;
|
||||||
|
return file ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canUpdateSelectedFolder(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
const folder = context.selection.folder;
|
||||||
|
if (folder && folder.entry) {
|
||||||
|
return nodeHasPermission(folder.entry, 'update');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nodeHasPermission(node: Node, permission: string): boolean {
|
||||||
|
if (node && permission) {
|
||||||
|
const allowableOperations = node.allowableOperations || [];
|
||||||
|
return allowableOperations.includes(permission);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
47
src/app/extensions/rules/core.evaluators.ts
Normal file
47
src/app/extensions/rules/core.evaluators.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RuleContext } from './rule-context';
|
||||||
|
import { RuleParameter } from './rule-parameter';
|
||||||
|
|
||||||
|
export function every(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
if (!args || args.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
.map(arg => context.evaluators[arg.value])
|
||||||
|
.every(evaluator => evaluator(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function some(context: RuleContext, ...args: RuleParameter[]): boolean {
|
||||||
|
if (!args || args.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
.map(arg => context.evaluators[arg.value])
|
||||||
|
.some(evaluator => evaluator(context));
|
||||||
|
}
|
34
src/app/extensions/rules/rule-context.ts
Normal file
34
src/app/extensions/rules/rule-context.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { SelectionState } from '../../store/states';
|
||||||
|
import { RuleEvaluator } from './rule.service';
|
||||||
|
import { NavigationState } from '../../store/states/navigation.state';
|
||||||
|
|
||||||
|
export interface RuleContext {
|
||||||
|
selection: SelectionState;
|
||||||
|
navigation: NavigationState;
|
||||||
|
evaluators: { [key: string]: RuleEvaluator };
|
||||||
|
}
|
29
src/app/extensions/rules/rule-parameter.ts
Normal file
29
src/app/extensions/rules/rule-parameter.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface RuleParameter {
|
||||||
|
type: string;
|
||||||
|
value: any;
|
||||||
|
}
|
34
src/app/extensions/rules/rule-ref.ts
Normal file
34
src/app/extensions/rules/rule-ref.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RuleParameter } from './rule-parameter';
|
||||||
|
import { RuleEvaluator } from './rule.service';
|
||||||
|
|
||||||
|
export class RuleRef {
|
||||||
|
type: string;
|
||||||
|
id?: string;
|
||||||
|
parameters?: Array<RuleParameter>;
|
||||||
|
evaluator?: RuleEvaluator;
|
||||||
|
}
|
97
src/app/extensions/rules/rule.service.ts
Normal file
97
src/app/extensions/rules/rule.service.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
|
import { every, some } from './core.evaluators';
|
||||||
|
import { RuleContext } from './rule-context';
|
||||||
|
import { RuleRef } from './rule-ref';
|
||||||
|
import { createSelector, Store } from '@ngrx/store';
|
||||||
|
import {
|
||||||
|
appSelection,
|
||||||
|
appNavigation
|
||||||
|
} from '../../store/selectors/app.selectors';
|
||||||
|
import { AppStore, SelectionState } from '../../store/states';
|
||||||
|
import { NavigationState } from '../../store/states/navigation.state';
|
||||||
|
import { canCreateFolder, hasFolderSelected, canUpdateSelectedFolder, hasFileSelected, canDownloadSelection } from './app.evaluators';
|
||||||
|
|
||||||
|
export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean;
|
||||||
|
|
||||||
|
export const selectionWithFolder = createSelector(
|
||||||
|
appSelection,
|
||||||
|
appNavigation,
|
||||||
|
(selection, navigation) => {
|
||||||
|
return {
|
||||||
|
selection,
|
||||||
|
navigation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RuleService implements RuleContext {
|
||||||
|
rules: Array<RuleRef> = [];
|
||||||
|
evaluators: { [key: string]: RuleEvaluator } = {};
|
||||||
|
selection: SelectionState;
|
||||||
|
navigation: NavigationState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private config: AppConfigService,
|
||||||
|
private store: Store<AppStore>
|
||||||
|
) {
|
||||||
|
this.evaluators['core.every'] = every;
|
||||||
|
this.evaluators['core.some'] = some;
|
||||||
|
this.evaluators['app.selection.canDownload'] = canDownloadSelection;
|
||||||
|
this.evaluators['app.selection.file'] = hasFileSelected;
|
||||||
|
this.evaluators['app.selection.folder'] = hasFolderSelected;
|
||||||
|
this.evaluators['app.selection.folder.canUpdate'] = canUpdateSelectedFolder;
|
||||||
|
this.evaluators['app.navigation.folder.canCreate'] = canCreateFolder;
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.select(selectionWithFolder)
|
||||||
|
.subscribe(result => {
|
||||||
|
this.selection = result.selection;
|
||||||
|
this.navigation = result.navigation;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.rules = this.config
|
||||||
|
.get<Array<RuleRef>>('extensions.core.rules', [])
|
||||||
|
.map(rule => {
|
||||||
|
rule.evaluator = this.evaluators[rule.type];
|
||||||
|
return rule;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateRule(ruleId: string): boolean {
|
||||||
|
const ruleRef = this.rules.find(ref => ref.id === ruleId);
|
||||||
|
|
||||||
|
if (ruleRef.evaluator) {
|
||||||
|
return ruleRef.evaluator(this, ...ruleRef.parameters);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -34,4 +34,5 @@ export const appSelection = createSelector(selectApp, state => state.selection)
|
|||||||
export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker);
|
export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker);
|
||||||
export const selectUser = createSelector(selectApp, state => state.user);
|
export const selectUser = createSelector(selectApp, state => state.user);
|
||||||
export const sharedUrl = createSelector(selectApp, state => state.sharedUrl);
|
export const sharedUrl = createSelector(selectApp, state => state.sharedUrl);
|
||||||
|
export const appNavigation = createSelector(selectApp, state => state.navigation);
|
||||||
export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder);
|
export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder);
|
||||||
|
@@ -60,6 +60,7 @@ import { NodeActionsService } from '../common/services/node-actions.service';
|
|||||||
import { NodePermissionService } from '../common/services/node-permission.service';
|
import { NodePermissionService } from '../common/services/node-permission.service';
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
import { ContentApiService } from '../services/content-api.service';
|
||||||
import { ExtensionService } from '../extensions/extension.service';
|
import { ExtensionService } from '../extensions/extension.service';
|
||||||
|
import { RuleService } from '../extensions/rules/rule.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -112,7 +113,8 @@ import { ExtensionService } from '../extensions/extension.service';
|
|||||||
NodeActionsService,
|
NodeActionsService,
|
||||||
NodePermissionService,
|
NodePermissionService,
|
||||||
ContentApiService,
|
ContentApiService,
|
||||||
ExtensionService
|
ExtensionService,
|
||||||
|
RuleService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppTestingModule {}
|
export class AppTestingModule {}
|
||||||
|
Reference in New Issue
Block a user