check permissions for motions

This commit is contained in:
Maximilian Krambach 2019-01-18 17:13:31 +01:00 committed by Emanuel Schütze
parent 2d40072f44
commit fb51b54d5c
9 changed files with 440 additions and 294 deletions

View File

@ -1,17 +1,24 @@
<h4 translate> <h4 translate>
<span translate>Submitters</span> <span translate>Submitters</span>
<button class="small-button" type="button" mat-icon-button disableRipple *ngIf="!isEditMode" (click)="onEdit()"> <button
class="small-button"
type="button"
mat-icon-button
disableRipple
*ngIf="!isEditMode && perms.isAllowed('change_metadata')"
(click)="onEdit()"
>
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
</h4> </h4>
<div *ngIf="!isEditMode"> <div *ngIf="!isEditMode || !perms.isAllowed('change_metadata')">
<mat-chip-list *ngFor="let submitter of motion.submitters" class="user"> <mat-chip-list *ngFor="let submitter of motion.submitters" class="user">
<mat-chip>{{ submitter.full_name }}</mat-chip> <mat-chip>{{ submitter.full_name }}</mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
<div *ngIf="isEditMode"> <div *ngIf="isEditMode && perms.isAllowed('change_metadata')">
<os-sorting-list <os-sorting-list
[input]="editSubmitterObservable" [input]="editSubmitterObservable"
[live]="true" [live]="true"

View File

@ -1,16 +1,17 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl } from '@angular/forms';
import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MatSnackBar } from '@angular/material';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { ViewMotion } from '../../models/view-motion';
import { User } from 'app/shared/models/users/user';
import { DataStoreService } from 'app/core/services/data-store.service';
import { MotionRepositoryService } from '../../services/motion-repository.service';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { DataStoreService } from 'app/core/services/data-store.service';
import { LocalPermissionsService } from '../../services/local-permissions.service';
import { MotionRepositoryService } from '../../services/motion-repository.service';
import { User } from 'app/shared/models/users/user';
import { ViewMotion } from '../../models/view-motion';
/** /**
* Component for the motion comments view * Component for the motion comments view
@ -61,13 +62,15 @@ export class ManageSubmittersComponent extends BaseViewComponent {
* @param matSnackBar * @param matSnackBar
* @param DS * @param DS
* @param repo * @param repo
* @param perms permission checks for the motion
*/ */
public constructor( public constructor(
title: Title, title: Title,
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private DS: DataStoreService, private DS: DataStoreService,
private repo: MotionRepositoryService private repo: MotionRepositoryService,
public perms: LocalPermissionsService
) { ) {
super(title, translate, matSnackBar); super(title, translate, matSnackBar);

View File

@ -1,5 +1,5 @@
<os-head-bar <os-head-bar
[mainButton]="opCanEdit()" [mainButton]="perms.isAllowed('update_motion', motion)"
mainButtonIcon="edit" mainButtonIcon="edit"
[nav]="false" [nav]="false"
[editMode]="editMotion" [editMode]="editMotion"
@ -61,13 +61,12 @@
<mat-menu #motionExtraMenu="matMenu"> <mat-menu #motionExtraMenu="matMenu">
<div *ngIf="motion"> <div *ngIf="motion">
<!-- PDF --> <!-- PDF -->
<button mat-menu-item <button mat-menu-item (click)="onDownloadPdf()">
(click)="onDownloadPdf()">
<mat-icon>picture_as_pdf</mat-icon> <mat-icon>picture_as_pdf</mat-icon>
<span translate>PDF</span> <span translate>PDF</span>
</button> </button>
<!-- List of speakers --> <!-- List of speakers -->
<button mat-menu-item [routerLink]="getSpeakerLink()"> <button mat-menu-item [routerLink]="getSpeakerLink()" *osPerms="'agenda.can_see'">
<mat-icon>mic</mat-icon> <mat-icon>mic</mat-icon>
<span translate>List of speakers</span> <span translate>List of speakers</span>
</button> </button>
@ -80,7 +79,7 @@
<button <button
mat-menu-item mat-menu-item
(click)="createAmendment()" (click)="createAmendment()"
*ngIf="amendmentsEnabled && motion && !motion.isParagraphBasedAmendment()" *ngIf="perms.isAllowed('can_create_amendments', motion)"
> >
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
<span translate>New amendment</span> <span translate>New amendment</span>
@ -95,14 +94,15 @@
<span translate>Show entire motion text</span> <span translate>Show entire motion text</span>
</button> </button>
<div *ngIf="perms.isAllowed('manage')">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<!-- Delete --> <!-- Delete -->
<button mat-menu-item class="red-warning-text" (click)="deleteMotionButton()"> <button mat-menu-item class="red-warning-text" (click)="deleteMotionButton()">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
<span translate>Delete</span> <span translate>Delete</span>
</button> </button>
</div> </div>
</div>
</mat-menu> </mat-menu>
</os-head-bar> </os-head-bar>
@ -110,9 +110,7 @@
<!-- Title --> <!-- Title -->
<div class="title on-transition-fade" *ngIf="motion && !editMotion"> <div class="title on-transition-fade" *ngIf="motion && !editMotion">
<div class="title-line"> <div class="title-line">
<h1> <h1>{{ motion.title }}</h1>
{{ motion.title }}
</h1>
<button mat-icon-button color="primary" (click)="toggleFavorite()"> <button mat-icon-button color="primary" (click)="toggleFavorite()">
<mat-icon>{{ motion.star ? 'star' : 'star_border' }}</mat-icon> <mat-icon>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
</button> </button>
@ -124,18 +122,13 @@
</div> </div>
<ng-template #mobileView> <ng-template #mobileView>
<!-- Meta info --> <!-- Meta info -->
<div class="hspacing"> <div class="hspacing"><ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container></div>
<ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container>
</div>
<mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider> <mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider>
<!-- Content --> <!-- Content -->
<div class="hspacing"> <div class="hspacing"><ng-container *ngTemplateOutlet="contentTemplate"></ng-container></div>
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
<mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider> <mat-divider class="spacer-top-10 spacer-bottom-20"></mat-divider>
@ -145,11 +138,14 @@
<!-- Personal note --> <!-- Personal note -->
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note> <os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
<!-- Motoin log --> <ng-container *ngTemplateOutlet="motionLogTemplate"></ng-container>
<button mat-button *ngIf="canShowLog" (click)="motionLogExpanded =!motionLogExpanded"> </ng-template>
<ng-template #motionLogTemplate>
<button mat-button *ngIf="canShowLog" (click)="motionLogExpanded = !motionLogExpanded">
<span translate>Show motion log</span> <span translate>Show motion log</span>
</button> </button>
<os-motion-log *ngIf="motionLogExpanded" [motion]="motion"></os-motion-log> <os-motion-log *ngIf="canShowLog && motionLogExpanded" [motion]="motion"></os-motion-log>
</ng-template> </ng-template>
<ng-template #desktopView> <ng-template #desktopView>
@ -162,10 +158,7 @@
<os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments> <os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note> <os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
<button mat-button *ngIf="canShowLog" (click)="motionLogExpanded =!motionLogExpanded"> <ng-container *ngTemplateOutlet="motionLogTemplate"></ng-container>
<span translate>Show motion log</span>
</button>
<os-motion-log *ngIf="motionLogExpanded" [motion]="motion"></os-motion-log>
</div> </div>
<div class="desktop-right "> <div class="desktop-right ">
<!-- Content --> <!-- Content -->
@ -229,24 +222,23 @@
{{ state.name | translate }} {{ state.name | translate }}
</button> </button>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="setState(null)"> <button mat-menu-item (click)="setState(null)" *ngIf="perms.isAllowed('change_metadata', motion)">
<mat-icon>replay</mat-icon> {{ 'Reset state' | translate }} <mat-icon>replay</mat-icon> {{ 'Reset state' | translate }}
</button> </button>
</mat-menu> </mat-menu>
<mat-basic-chip <mat-basic-chip
*ngIf="motion.state" *ngIf="perms.isAllowed('change_metadata', motion)"
[matMenuTriggerFor]="stateMenu" [matMenuTriggerFor]="stateMenu"
[ngClass]="{ [ngClass]="getStateCssColor()"
green: motion.state.css_class === 'success', >
red: motion.state.css_class === 'danger', {{ motion.state.name | translate }}
grey: motion.state.css_class === 'default', </mat-basic-chip>
lightblue: motion.state.css_class === 'primary' <mat-basic-chip
}" *ngIf="!perms.isAllowed('change_metadata', motion)"
[ngClass]="getStateCssColor()"
> >
{{ motion.state.name | translate }} {{ motion.state.name | translate }}
</mat-basic-chip> </mat-basic-chip>
<!--*osPerms="['motions.can_manage', 'motions.can_manage_metadata']; -->
</div> </div>
<!-- Recommendation --> <!-- Recommendation -->
@ -261,17 +253,33 @@
{{ recommendation.recommendation_label | translate }} {{ recommendation.recommendation_label | translate }}
</button> </button>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="setRecommendation(null)"> <button mat-menu-item *ngIf="perms.isAllowed('change_metadata', motion)"
(click)="setRecommendation(null)">
<mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }} <mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }}
</button> </button>
</mat-menu> </mat-menu>
<mat-basic-chip [matMenuTriggerFor]="recommendationMenu" class="bluegrey"> <mat-basic-chip
*ngIf="perms.isAllowed('change_metadata', motion)"
[matMenuTriggerFor]="recommendationMenu"
class="bluegrey"
>
{{ {{
motion.recommendation motion.recommendation
? (motion.recommendation.recommendation_label | translate) ? (motion.recommendation.recommendation_label | translate)
: ('not set' | translate) : ('not set' | translate)
}} }}
</mat-basic-chip> </mat-basic-chip>
<mat-basic-chip
*ngIf="!perms.isAllowed('change_metadata', motion)"
class="bluegrey"
>
{{
motion.recommendation
? (motion.recommendation.recommendation_label | translate)
: ('not set' | translate)
}}
</mat-basic-chip>
<button mat-button *ngIf="canFollowRecommendation()" (click)="onFollowRecButton()"> <button mat-button *ngIf="canFollowRecommendation()" (click)="onFollowRecButton()">
<span translate>Follow recommendation</span> <span translate>Follow recommendation</span>
</button> </button>
@ -291,7 +299,10 @@
{{ category }} {{ category }}
</button> </button>
</mat-menu> </mat-menu>
<mat-basic-chip [matMenuTriggerFor]="categoryMenu" class="grey"> <mat-basic-chip *ngIf="perms.isAllowed('change_metadata', motion)" [matMenuTriggerFor]="categoryMenu" class="grey">
{{ motion.category ? motion.category : '' }}
</mat-basic-chip>
<mat-basic-chip *ngIf="!perms.isAllowed('change_metadata', motion)" class="grey">
{{ motion.category ? motion.category : '' }} {{ motion.category ? motion.category : '' }}
</mat-basic-chip> </mat-basic-chip>
</div> </div>
@ -305,7 +316,10 @@
{{ block }} {{ block }}
</button> </button>
</mat-menu> </mat-menu>
<mat-basic-chip [matMenuTriggerFor]="blockMenu" class="grey"> <mat-basic-chip *ngIf="perms.isAllowed('change_metadata', motion)" [matMenuTriggerFor]="blockMenu" class="grey">
{{ motion.motion_block ? motion.motion_block : '' }}
</mat-basic-chip>
<mat-basic-chip *ngIf="!perms.isAllowed('change_metadata', motion)" class="grey">
{{ motion.motion_block ? motion.motion_block : '' }} {{ motion.motion_block ? motion.motion_block : '' }}
</mat-basic-chip> </mat-basic-chip>
</div> </div>
@ -380,7 +394,7 @@
<!-- Submitter --> <!-- Submitter -->
<div *ngIf="newMotion" class="content-field"> <div *ngIf="newMotion" class="content-field">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']"> <div *ngIf="perms.isAllowed('change_metadata', motion)">
<os-search-value-selector <os-search-value-selector
ngDefaultControl ngDefaultControl
[form]="contentForm" [form]="contentForm"
@ -537,7 +551,7 @@
<!-- Supporter form --> <!-- Supporter form -->
<div class="content-field" *ngIf="editMotion && minSupporters"> <div class="content-field" *ngIf="editMotion && minSupporters">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']"> <div *ngIf="perms.isAllowed('change_metadata', motion)">
<os-search-value-selector <os-search-value-selector
ngDefaultControl ngDefaultControl
[form]="contentForm" [form]="contentForm"
@ -551,7 +565,7 @@
<!-- Workflow --> <!-- Workflow -->
<div class="content-field" *ngIf="editMotion && workflowObserver.value.length > 1"> <div class="content-field" *ngIf="editMotion && workflowObserver.value.length > 1">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']"> <div *ngIf="perms.isAllowed('change_metadata', motion)">
<os-search-value-selector <os-search-value-selector
ngDefaultControl ngDefaultControl
[form]="contentForm" [form]="contentForm"
@ -565,7 +579,7 @@
<!-- Origin form --> <!-- Origin form -->
<div class="content-field" *ngIf="editMotion"> <div class="content-field" *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']"> <div *ngIf="perms.isAllowed('change_metadata', motion)">
<mat-form-field> <mat-form-field>
<input <input
matInput matInput

View File

@ -1,45 +1,43 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, Subscription, ReplaySubject, concat } from 'rxjs';
import { Component, OnInit, ViewChild } from '@angular/core';
import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog, MatExpansionPanel, MatSnackBar, MatCheckboxChange } from '@angular/material'; import { MatDialog, MatExpansionPanel, MatSnackBar, MatCheckboxChange } from '@angular/material';
import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators'; import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
import { Category } from '../../../../shared/models/motions/category';
import { ViewportService } from '../../../../core/services/viewport.service';
import { MotionRepositoryService } from '../../services/motion-repository.service';
import { ChangeRecoMode, LineNumberingMode, ViewMotion } from '../../models/view-motion';
import { User } from '../../../../shared/models/users/user';
import { DataStoreService } from '../../../../core/services/data-store.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Motion } from '../../../../shared/models/motions/motion';
import { BehaviorSubject, Subscription, ReplaySubject, concat } from 'rxjs'; import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service';
import { BaseViewComponent } from '../../../base/base-view';
import { Category } from '../../../../shared/models/motions/category';
import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service';
import { ChangeRecoMode, LineNumberingMode, ViewMotion } from '../../models/view-motion';
import { CreateMotion } from '../../models/create-motion';
import { ConfigService } from '../../../../core/services/config.service';
import { DataStoreService } from '../../../../core/services/data-store.service';
import { DiffLinesInParagraph, LineRange } from '../../services/diff.service'; import { DiffLinesInParagraph, LineRange } from '../../services/diff.service';
import { itemVisibilityChoices, Item } from 'app/shared/models/agenda/item';
import { LocalPermissionsService } from '../../services/local-permissions.service';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Motion } from '../../../../shared/models/motions/motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { import {
MotionChangeRecommendationComponent, MotionChangeRecommendationComponent,
MotionChangeRecommendationComponentData MotionChangeRecommendationComponentData
} from '../motion-change-recommendation/motion-change-recommendation.component'; } from '../motion-change-recommendation/motion-change-recommendation.component';
import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service';
import { ViewChangeReco } from '../../models/view-change-reco';
import { ViewUnifiedChange } from '../../models/view-unified-change';
import { OperatorService } from '../../../../core/services/operator.service';
import { BaseViewComponent } from '../../../base/base-view';
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
import { StatuteParagraphRepositoryService } from '../../services/statute-paragraph-repository.service';
import { ConfigService } from '../../../../core/services/config.service';
import { Workflow } from 'app/shared/models/motions/workflow';
import { LocalPermissionsService } from '../../services/local-permissions.service';
import { ViewCreateMotion } from '../../models/view-create-motion';
import { CreateMotion } from '../../models/create-motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { itemVisibilityChoices, Item } from 'app/shared/models/agenda/item';
import { PromptService } from 'app/core/services/prompt.service';
import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { MotionPdfExportService } from '../../services/motion-pdf-export.service'; import { MotionPdfExportService } from '../../services/motion-pdf-export.service';
import { PersonalNoteService } from '../../services/personal-note.service'; import { MotionRepositoryService } from '../../services/motion-repository.service';
import { PersonalNoteContent } from 'app/shared/models/users/personal-note'; import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { PersonalNoteService } from '../../services/personal-note.service';
import { PromptService } from 'app/core/services/prompt.service';
import { StatuteParagraphRepositoryService } from '../../services/statute-paragraph-repository.service';
import { User } from '../../../../shared/models/users/user';
import { ViewChangeReco } from '../../models/view-change-reco';
import { ViewCreateMotion } from '../../models/view-create-motion';
import { ViewportService } from '../../../../core/services/viewport.service';
import { ViewUnifiedChange } from '../../models/view-unified-change';
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
import { Workflow } from 'app/shared/models/motions/workflow';
/** /**
* Component for the motion detail view * Component for the motion detail view
@ -107,6 +105,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
if ( if (
this.motion && this.motion &&
!this.editMotion && !this.editMotion &&
this.perms.isAllowed('manage') &&
this.motion.motion.log_messages && this.motion.motion.log_messages &&
this.motion.motion.log_messages.length this.motion.motion.log_messages.length
) { ) {
@ -316,7 +315,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
public vp: ViewportService, public vp: ViewportService,
public perms: LocalPermissionsService, public perms: LocalPermissionsService,
private op: OperatorService,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -984,15 +982,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
} }
/** /**
* Determine if the user has the correct requirements to alter the motion * Handler for creating a poll
* TODO: All views should probably have a "isAllowedTo" routine to simplify this process
*
* @returns whether or not the OP is allowed to edit the motion
*/ */
public opCanEdit(): boolean {
return this.op.hasPerms('motions.can_manage', 'motions.can_manage_metadata');
}
public async createPoll(): Promise<void> { public async createPoll(): Promise<void> {
await this.repo.createPoll(this.motion); await this.repo.createPoll(this.motion);
} }
@ -1000,7 +991,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
/** /**
* Check if a recommendation can be followed. Checks for permissions and additionally if a recommentadion is present * Check if a recommendation can be followed. Checks for permissions and additionally if a recommentadion is present
*/ */
public get canFollowRecommendation(): boolean { public canFollowRecommendation(): boolean {
if ( if (
this.perms.isAllowed('createPoll', this.motion) && this.perms.isAllowed('createPoll', this.motion) &&
this.motion.recommendation && this.motion.recommendation &&
@ -1024,4 +1015,24 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
public async toggleFavorite(): Promise<void> { public async toggleFavorite(): Promise<void> {
this.personalNoteService.setPersonalNoteStar(this.motion.motion, !this.motion.star); this.personalNoteService.setPersonalNoteStar(this.motion.motion, !this.motion.star);
} }
/**
* Translate the state's css class into a color
*
* @returns a string representing a color
*/
public getStateCssColor(): string {
switch (this.motion.state.css_class) {
case 'success':
return 'green';
case 'danger':
return 'red';
case 'default':
return 'grey';
case 'primary':
return 'lightblue';
default:
return '';
}
}
} }

View File

@ -1,4 +1,4 @@
<os-head-bar [mainButton]="true" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect"> <os-head-bar [mainButton]="perms.isAllowed('create')" (mainEvent)="onPlusButton()" [multiSelectMode]="isMultiSelect">
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Motions</h2></div> <div class="title-slot"><h2 translate>Motions</h2></div>
@ -17,11 +17,11 @@
</os-head-bar> </os-head-bar>
<mat-drawer-container class="on-transition-fade"> <mat-drawer-container class="on-transition-fade">
<os-sort-filter-bar [filterService]="filterService" [sortService]="sortService" <os-sort-filter-bar [filterService]="filterService" [sortService]="sortService"
(searchFieldChange)="searchFilter($event)"> (searchFieldChange)="searchFilter($event)">
</os-sort-filter-bar> </os-sort-filter-bar>
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort> <mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Selector column --> <!-- Selector column -->
<ng-container matColumnDef="selector"> <ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
@ -123,17 +123,21 @@
class="lg" class="lg"
> >
</mat-row> </mat-row>
</mat-table> </mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator> <mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
</mat-drawer-container>
<mat-menu #motionListMenu="matMenu"> <mat-menu #motionListMenu="matMenu">
<div *ngIf="!isMultiSelect"> <div *ngIf="!isMultiSelect">
<button mat-menu-item *osPerms="'motions.can_manage'" (click)="toggleMultiSelect()"> <div *ngIf="perms.isAllowed('change_metadata')">
<button mat-menu-item (click)="toggleMultiSelect()">
<mat-icon>library_add</mat-icon> <mat-icon>library_add</mat-icon>
<span translate>Multiselect</span> <span translate>Multiselect</span>
</button> </button>
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="call-list"> </div>
<div *ngIf="perms.isAllowed('manage')">
<button mat-menu-item routerLink="call-list">
<mat-icon>sort</mat-icon> <mat-icon>sort</mat-icon>
<span translate>Call list</span> <span translate>Call list</span>
</button> </button>
@ -161,11 +165,12 @@
<mat-icon>archive</mat-icon> <mat-icon>archive</mat-icon>
<span translate>Export as CSV</span> <span translate>Export as CSV</span>
</button> </button>
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="import"> <button mat-menu-item routerLink="import">
<mat-icon>save_alt</mat-icon> <mat-icon>save_alt</mat-icon>
<span translate>Import</span><span>&nbsp;...</span> <span translate>Import</span><span>&nbsp;...</span>
</button> </button>
</div> </div>
</div>
<div *ngIf="isMultiSelect"> <div *ngIf="isMultiSelect">
<button mat-menu-item (click)="selectAll()"> <button mat-menu-item (click)="selectAll()">
<mat-icon>done_all</mat-icon> <mat-icon>done_all</mat-icon>
@ -175,7 +180,7 @@
<mat-icon>clear</mat-icon> <mat-icon>clear</mat-icon>
<span translate>Deselect all</span> <span translate>Deselect all</span>
</button> </button>
<div *osPerms="'motions.can_manage'"> <div *ngIf="perms.isAllowed('change_metadata')">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))">
<mat-icon>label</mat-icon> <mat-icon>label</mat-icon>
@ -217,6 +222,7 @@
<span translate>Move to agenda item</span> <span translate>Move to agenda item</span>
</button> </button>
</div> </div>
<div *ngIf="perms.isAllowed('manage')">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button <button
mat-menu-item mat-menu-item
@ -227,5 +233,6 @@
<span translate>Delete</span> <span translate>Delete</span>
</button> </button>
</div> </div>
</div>
</mat-menu> </mat-menu>
</mat-drawer-container>

View File

@ -3,22 +3,23 @@ import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ConfigService } from '../../../../core/services/config.service';
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
import { ListViewBaseComponent } from '../../../base/list-view-base';
import { MatSnackBar } from '@angular/material';
import { ViewMotion } from '../../models/view-motion';
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
import { MotionMultiselectService } from '../../services/motion-multiselect.service';
import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service';
import { CategoryRepositoryService } from '../../services/category-repository.service'; import { CategoryRepositoryService } from '../../services/category-repository.service';
import { ConfigService } from '../../../../core/services/config.service';
import { ListViewBaseComponent } from '../../../base/list-view-base';
import { LocalPermissionsService } from '../../services/local-permissions.service';
import { MatSnackBar } from '@angular/material';
import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service'; import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service';
import { MotionCsvExportService } from '../../services/motion-csv-export.service';
import { MotionFilterListService } from '../../services/motion-filter-list.service'; import { MotionFilterListService } from '../../services/motion-filter-list.service';
import { MotionMultiselectService } from '../../services/motion-multiselect.service';
import { MotionSortListService } from '../../services/motion-sort-list.service'; import { MotionSortListService } from '../../services/motion-sort-list.service';
import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service';
import { ViewCategory } from '../../models/view-category';
import { ViewMotion } from '../../models/view-motion';
import { ViewMotionBlock } from '../../models/view-motion-block';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewWorkflow } from '../../models/view-workflow'; import { ViewWorkflow } from '../../models/view-workflow';
import { ViewCategory } from '../../models/view-category'; import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
import { ViewMotionBlock } from '../../models/view-motion-block';
import { WorkflowRepositoryService } from '../../services/workflow-repository.service'; import { WorkflowRepositoryService } from '../../services/workflow-repository.service';
/** /**
@ -75,6 +76,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* @param userRepo * @param userRepo
* @param sortService * @param sortService
* @param filterService * @param filterService
* @param perms LocalPermissionService
*/ */
public constructor( public constructor(
titleService: Title, titleService: Title,
@ -90,7 +92,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
private motionCsvExport: MotionCsvExportService, private motionCsvExport: MotionCsvExportService,
public multiselectService: MotionMultiselectService, public multiselectService: MotionMultiselectService,
public sortService: MotionSortListService, public sortService: MotionSortListService,
public filterService: MotionFilterListService public filterService: MotionFilterListService,
public perms: LocalPermissionsService
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);

View File

@ -6,10 +6,13 @@
<div *ngIf="poll.has_votes" class="on-transition-fade poll-result"> <div *ngIf="poll.has_votes" class="on-transition-fade poll-result">
<div *ngFor="let key of pollValues"> <div *ngFor="let key of pollValues">
<div class="poll-progress on-transition-fade" *ngIf="poll[key] !== undefined"> <div class="poll-progress on-transition-fade" *ngIf="poll[key] !== undefined">
<mat-icon class="main-nav-color" matTooltip="{{ getLabel(key) | translate }}"> {{ getIcon(key) }} </mat-icon> <mat-icon class="main-nav-color" matTooltip="{{ getLabel(key) | translate }}">
{{ getIcon(key) }}
</mat-icon>
<div class="progress-container"> <div class="progress-container">
<div> <div>
<span translate>{{ getLabel(key) }}</span>:&nbsp;{{ getNumber(key) }} <span translate>{{ getLabel(key) }}</span
>:&nbsp;{{ getNumber(key) }}
<span *ngIf="!isAbstractValue(key)">({{ getPercent(key) }}%)</span> <span *ngIf="!isAbstractValue(key)">({{ getPercent(key) }}%)</span>
</div> </div>
<div *ngIf="!isAbstractValue(key)" class="poll-progress-bar"> <div *ngIf="!isAbstractValue(key)" class="poll-progress-bar">
@ -22,9 +25,10 @@
</div> </div>
</div> </div>
</div> </div>
<hr *ngIf="key ==='abstain'" flex /> <hr *ngIf="key === 'abstain'" flex />
</div> </div>
<!-- quorum --> <!-- quorum -->
<div *osPerms="'motions.can_manage'">
<div *ngIf="abstractPoll"><span translate>Quorum not calculable.</span></div> <div *ngIf="abstractPoll"><span translate>Quorum not calculable.</span></div>
<div class="poll-quorum-line" *ngIf="!abstractPoll"> <div class="poll-quorum-line" *ngIf="!abstractPoll">
<span> <span>
@ -33,8 +37,10 @@
<mat-icon color="primary" *ngIf="quorumYesReached"> thumb_up </mat-icon> <mat-icon color="primary" *ngIf="quorumYesReached"> thumb_up </mat-icon>
</span> </span>
<button mat-button [matMenuTriggerFor]="majorityMenu"> <button mat-button [matMenuTriggerFor]="majorityMenu">
&nbsp;<span translate>{{ getQuorumLabel() }}</span> &nbsp;<span translate>{{ getQuorumLabel() }}</span> &nbsp;<span
&nbsp;<span *ngIf="majorityChoice !== 'disabled'">({{ yesQuorum }})</span> *ngIf="majorityChoice !== 'disabled'"
>({{ yesQuorum }})</span
>
</button> </button>
<span *ngIf="majorityChoice !== 'disabled'"> <span *ngIf="majorityChoice !== 'disabled'">
<span *ngIf="quorumYesReached" translate> reached.</span> <span *ngIf="quorumYesReached" translate> reached.</span>
@ -46,15 +52,26 @@
</span> </span>
</div> </div>
</div> </div>
</div>
</ng-container> </ng-container>
<ng-container class="meta-text-block-action-row" *osPerms="'motions.can_manage'"> <ng-container class="meta-text-block-action-row" *osPerms="'motions.can_manage_metadata'">
<button mat-icon-button class="main-nav-color" matTooltip="{{ 'Edit poll' | translate }}" (click)="editPoll()"> <button mat-icon-button class="main-nav-color" matTooltip="{{ 'Edit poll' | translate }}" (click)="editPoll()">
<mat-icon inline>edit</mat-icon> <mat-icon inline>edit</mat-icon>
</button> </button>
<button mat-icon-button class="main-nav-color" matTooltip="{{ 'Print ballots' | translate }}" (click)="printBallots()"> <button
mat-icon-button
class="main-nav-color"
matTooltip="{{ 'Print ballots' | translate }}"
(click)="printBallots()"
>
<mat-icon inline>local_printshop</mat-icon> <mat-icon inline>local_printshop</mat-icon>
</button> </button>
<button mat-icon-button class="main-nav-color" matTooltip="{{ 'Delete poll' | translate }}" (click)="deletePoll()"> <button
mat-icon-button
class="main-nav-color"
matTooltip="{{ 'Delete poll' | translate }}"
(click)="deletePoll()"
>
<mat-icon inline>delete</mat-icon> <mat-icon inline>delete</mat-icon>
</button> </button>
</ng-container> </ng-container>

View File

@ -2,35 +2,60 @@ import { Injectable } from '@angular/core';
import { OperatorService } from '../../../core/services/operator.service'; import { OperatorService } from '../../../core/services/operator.service';
import { ViewMotion } from '../models/view-motion'; import { ViewMotion } from '../models/view-motion';
import { ConfigService } from '../../../core/services/config.service'; import { ConfigService } from '../../../core/services/config.service';
import { ConstantsService } from 'app/core/services/constants.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class LocalPermissionsService { export class LocalPermissionsService {
public configMinSupporters: number; public configMinSupporters: number;
private amendmentEnabled: boolean;
private amendmentOfAmendment: boolean;
public constructor(private operator: OperatorService, private configService: ConfigService) { public constructor(
private operator: OperatorService,
private configService: ConfigService,
private constants: ConstantsService
) {
// load config variables // load config variables
this.configService this.configService
.get('motions_min_supporters') .get('motions_min_supporters')
.subscribe(supporters => (this.configMinSupporters = supporters)); .subscribe(supporters => (this.configMinSupporters = supporters));
this.configService.get('motions_amendments_enabled').subscribe(enabled => (this.amendmentEnabled = enabled));
this.constants
.get('OpenSlidesSettings')
.subscribe(settings => (this.amendmentOfAmendment = settings.MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS));
} }
/** /**
* Should determine if the user (Operator) has the * Determine if the user (Operator) has the correct permission to perform the given action.
* correct permission to perform the given action.
* *
* actions might be: * actions might be:
* - create
* - support * - support
* - unsupport * - unsupport
* - createpoll * - createpoll
* - update
* - update_submitters
* - delete
* - change_metadata
* - reset_state
* - change_recommendation
* - can_create_amendments
* - can_manage_metadata
* - manage
* *
* @param action the action the user tries to perform * @param action the action the user tries to perform
* @param motion the motion for which to perform the action
*/ */
public isAllowed(action: string, motion?: ViewMotion): boolean { public isAllowed(action: string, motion?: ViewMotion): boolean {
if (motion) {
switch (action) { switch (action) {
case 'create':
return this.operator.hasPerms('motions.can_create');
case 'support': case 'support':
if (!motion) {
return false;
}
return ( return (
this.operator.hasPerms('motions.can_support') && this.operator.hasPerms('motions.can_support') &&
this.configMinSupporters > 0 && this.configMinSupporters > 0 &&
@ -39,12 +64,62 @@ export class LocalPermissionsService {
motion.supporters.indexOf(this.operator.user) === -1 motion.supporters.indexOf(this.operator.user) === -1
); );
case 'unsupport': case 'unsupport':
if (!motion) {
return false;
}
return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1; return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1;
case 'createpoll': case 'createpoll':
return this.operator.hasPerms('motions.can_manage') && motion.state.allow_create_poll; if (!motion) {
return false;
}
return (
(this.operator.hasPerms('motions.can_manage') ||
this.operator.hasPerms('motions.can_manage_metadata')) &&
motion.state.allow_create_poll
);
case 'update':
if (!motion) {
return false;
}
return (
this.operator.hasPerms('motions.can_manage') &&
motion.state.allow_submitter_edit &&
motion.submitters.some(submitter => submitter.id === this.operator.user.id)
);
case 'update_submitters':
return this.operator.hasPerms('motions.can_manage');
case 'delete':
if (!motion) {
return false;
}
return (
this.operator.hasPerms('motions.can_manage') &&
motion.state.allow_submitter_edit &&
motion.submitters.some(submitter => submitter.id === this.operator.user.id)
);
case 'change_metadata':
return (
this.operator.hasPerms('motions.can_manage') ||
this.operator.hasPerms('motions.can_manage_metadata')
);
case 'can_create_amendments':
if (!motion) {
return false;
}
return (
this.operator.hasPerms('motions.can_create_amendments') &&
this.amendmentEnabled &&
(!motion.parent_id || (motion.parent_id && this.amendmentOfAmendment))
);
case 'can_manage_metadata':
return (
this.operator.hasPerms('motions.can_manage') &&
this.operator.hasPerms('motions.can_manage_metadata')
);
case 'manage':
return this.operator.hasPerms('motions.can_manage');
default: default:
return false; return false;
} }
} }
}
} }

View File

@ -692,4 +692,13 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
await this.httpService.post(restPath); await this.httpService.post(restPath);
} }
} }
/**
* Check if a motion currently has any amendments
*
* @param motion A viewMotion
* @returns True if there is at eleast one amendment
*/
public hasAmendments(motion: ViewMotion): boolean {
return this.getViewModelList().filter(allMotions => allMotions.parent_id === motion.id).length > 0;
}
} }