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>
<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>
</button>
</h4>
<div *ngIf="!isEditMode">
<div *ngIf="!isEditMode || !perms.isAllowed('change_metadata')">
<mat-chip-list *ngFor="let submitter of motion.submitters" class="user">
<mat-chip>{{ submitter.full_name }}</mat-chip>
</mat-chip-list>
</div>
<div *ngIf="isEditMode">
<div *ngIf="isEditMode && perms.isAllowed('change_metadata')">
<os-sorting-list
[input]="editSubmitterObservable"
[live]="true"

View File

@ -1,16 +1,17 @@
import { Component, Input } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { MatSnackBar } from '@angular/material';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { MatSnackBar } from '@angular/material';
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 { 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
@ -61,13 +62,15 @@ export class ManageSubmittersComponent extends BaseViewComponent {
* @param matSnackBar
* @param DS
* @param repo
* @param perms permission checks for the motion
*/
public constructor(
title: Title,
translate: TranslateService,
matSnackBar: MatSnackBar,
private DS: DataStoreService,
private repo: MotionRepositoryService
private repo: MotionRepositoryService,
public perms: LocalPermissionsService
) {
super(title, translate, matSnackBar);

View File

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

View File

@ -1,45 +1,43 @@
import { Component, OnInit, ViewChild } from '@angular/core';
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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog, MatExpansionPanel, MatSnackBar, MatCheckboxChange } from '@angular/material';
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 { 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 { 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 {
MotionChangeRecommendationComponent,
MotionChangeRecommendationComponentData
} 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 { PersonalNoteService } from '../../services/personal-note.service';
import { MotionRepositoryService } from '../../services/motion-repository.service';
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
@ -107,6 +105,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
if (
this.motion &&
!this.editMotion &&
this.perms.isAllowed('manage') &&
this.motion.motion.log_messages &&
this.motion.motion.log_messages.length
) {
@ -316,7 +315,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
matSnackBar: MatSnackBar,
public vp: ViewportService,
public perms: LocalPermissionsService,
private op: OperatorService,
private router: Router,
private route: ActivatedRoute,
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
* 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
* Handler for creating a poll
*/
public opCanEdit(): boolean {
return this.op.hasPerms('motions.can_manage', 'motions.can_manage_metadata');
}
public async createPoll(): Promise<void> {
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
*/
public get canFollowRecommendation(): boolean {
public canFollowRecommendation(): boolean {
if (
this.perms.isAllowed('createPoll', this.motion) &&
this.motion.recommendation &&
@ -1024,4 +1015,24 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
public async toggleFavorite(): Promise<void> {
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 -->
<div class="title-slot"><h2 translate>Motions</h2></div>
@ -17,154 +17,159 @@
</os-head-bar>
<mat-drawer-container class="on-transition-fade">
<os-sort-filter-bar [filterService]="filterService" [sortService]="sortService"
(searchFieldChange)="searchFilter($event)">
</os-sort-filter-bar>
<os-sort-filter-bar [filterService]="filterService" [sortService]="sortService"
(searchFieldChange)="searchFilter($event)">
</os-sort-filter-bar>
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Selector column -->
<ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
<mat-cell *matCellDef="let motion" class="checkbox-cell">
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
</mat-cell>
</ng-container>
<mat-table class="os-listview-table on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Selector column -->
<ng-container matColumnDef="selector">
<mat-header-cell *matHeaderCellDef mat-sort-header class="checkbox-cell"></mat-header-cell>
<mat-cell *matCellDef="let motion" class="checkbox-cell">
<mat-icon>{{ isSelected(motion) ? 'check_circle' : '' }}</mat-icon>
</mat-cell>
</ng-container>
<!-- identifier column -->
<ng-container matColumnDef="identifier">
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">{{ motion.identifier }}</div>
</mat-cell>
</ng-container>
<!-- identifier column -->
<ng-container matColumnDef="identifier">
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">{{ motion.identifier }}</div>
</mat-cell>
</ng-container>
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">
<span class="motion-list-title">{{ motion.title }}
<span>
<mat-icon inline>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">
<span class="motion-list-title">{{ motion.title }}
<span>
<mat-icon inline>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
</span>
</span>
</span>
<!-- attachments -->
<span class="attached-files" *ngIf="motion.hasAttachments()">
<!-- <mat-basic-chip class="bluegrey"> <mat-icon>attach_file</mat-icon> </mat-basic-chip> -->
<mat-icon>attach_file</mat-icon>
</span>
<!-- attachments -->
<span class="attached-files" *ngIf="motion.hasAttachments()">
<!-- <mat-basic-chip class="bluegrey"> <mat-icon>attach_file</mat-icon> </mat-basic-chip> -->
<mat-icon>attach_file</mat-icon>
</span>
<br />
<span class="motion-list-from" *ngIf="motion.submitters.length">
<span translate>by</span> {{ motion.submitters }}
</span>
<br *ngIf="motion.submitters.length" />
<!-- state -->
<mat-basic-chip
*ngIf="motion.state"
[ngClass]="{
green: motion.state.css_class === 'success',
red: motion.state.css_class === 'danger',
grey: motion.state.css_class === 'default',
lightblue: motion.state.css_class === 'primary'
}"
>
{{ motion.state.name | translate }}
</mat-basic-chip>
<br />
<span class="motion-list-from" *ngIf="motion.submitters.length">
<span translate>by</span> {{ motion.submitters }}
</span>
<br *ngIf="motion.submitters.length" />
<!-- state -->
<mat-basic-chip
*ngIf="motion.state"
[ngClass]="{
green: motion.state.css_class === 'success',
red: motion.state.css_class === 'danger',
grey: motion.state.css_class === 'default',
lightblue: motion.state.css_class === 'primary'
}"
>
{{ motion.state.name | translate }}
</mat-basic-chip>
<!-- recommendation -->
<span *ngIf="motion.recommendation">
<mat-basic-chip class="bluegrey">{{
motion.recommendation.recommendation_label | translate
}}</mat-basic-chip>
</span>
</div>
</mat-cell>
</ng-container>
<!-- state column -->
<ng-container matColumnDef="state">
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">
<div class="small" *ngIf="motion.category">
<mat-icon>device_hub</mat-icon>
{{ motion.category }}
<!-- recommendation -->
<span *ngIf="motion.recommendation">
<mat-basic-chip class="bluegrey">{{
motion.recommendation.recommendation_label | translate
}}</mat-basic-chip>
</span>
</div>
<div class="small" *ngIf="motion.motion_block">
<mat-icon>widgets</mat-icon>
{{ motion.motion_block.title }}
</mat-cell>
</ng-container>
<!-- state column -->
<ng-container matColumnDef="state">
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">
<div class="small" *ngIf="motion.category">
<mat-icon>device_hub</mat-icon>
{{ motion.category }}
</div>
<div class="small" *ngIf="motion.motion_block">
<mat-icon>widgets</mat-icon>
{{ motion.motion_block.title }}
</div>
</div>
</div>
</mat-cell>
</ng-container>
</mat-cell>
</ng-container>
<!-- Speakers column -->
<ng-container matColumnDef="speakers">
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
<mat-cell *matCellDef="let motion">
<button mat-icon-button (click)="onSpeakerIcon(motion, $event)">
<mat-icon
[matBadge]="motion.agendaSpeakerAmount > 0 ? motion.agendaSpeakerAmount : null"
matBadgeColor="accent"
>
mic
</mat-icon>
</button>
</mat-cell>
</ng-container>
<!-- Speakers column -->
<ng-container matColumnDef="speakers">
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
<mat-cell *matCellDef="let motion">
<button mat-icon-button (click)="onSpeakerIcon(motion, $event)">
<mat-icon
[matBadge]="motion.agendaSpeakerAmount > 0 ? motion.agendaSpeakerAmount : null"
matBadgeColor="accent"
>
mic
</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
<mat-row
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
(click)="selectItem(row, $event)"
*matRowDef="let row; columns: getColumnDefinition()"
class="lg"
>
</mat-row>
</mat-table>
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
<mat-row
[ngClass]="selectedRows.indexOf(row) >= 0 ? 'selected' : ''"
(click)="selectItem(row, $event)"
*matRowDef="let row; columns: getColumnDefinition()"
class="lg"
>
</mat-row>
</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">
<div *ngIf="!isMultiSelect">
<button mat-menu-item *osPerms="'motions.can_manage'" (click)="toggleMultiSelect()">
<mat-icon>library_add</mat-icon>
<span translate>Multiselect</span>
</button>
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="call-list">
<mat-icon>sort</mat-icon>
<span translate>Call list</span>
</button>
<button mat-menu-item routerLink="category">
<mat-icon>device_hub</mat-icon>
<span translate>Categories</span>
</button>
<button mat-menu-item routerLink="blocks">
<mat-icon>widgets</mat-icon>
<span translate>Motion blocks</span>
</button>
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
<mat-icon>account_balance</mat-icon>
<span translate>Statute</span>
</button>
<button mat-menu-item routerLink="comment-section">
<mat-icon>speaker_notes</mat-icon>
<span translate>Comment fields</span>
</button>
<button mat-menu-item routerLink="/tags" *osPerms="'core.can_manage_tags'">
<mat-icon>local_offer</mat-icon>
<span translate>Tags</span>
</button>
<button mat-menu-item (click)="csvExportMotionList()">
<mat-icon>archive</mat-icon>
<span translate>Export as CSV</span>
</button>
<button mat-menu-item *osPerms="'motions.can_manage'" routerLink="import">
<mat-icon>save_alt</mat-icon>
<span translate>Import</span><span>&nbsp;...</span>
</button>
<div *ngIf="perms.isAllowed('change_metadata')">
<button mat-menu-item (click)="toggleMultiSelect()">
<mat-icon>library_add</mat-icon>
<span translate>Multiselect</span>
</button>
</div>
<div *ngIf="perms.isAllowed('manage')">
<button mat-menu-item routerLink="call-list">
<mat-icon>sort</mat-icon>
<span translate>Call list</span>
</button>
<button mat-menu-item routerLink="category">
<mat-icon>device_hub</mat-icon>
<span translate>Categories</span>
</button>
<button mat-menu-item routerLink="blocks">
<mat-icon>widgets</mat-icon>
<span translate>Motion blocks</span>
</button>
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
<mat-icon>account_balance</mat-icon>
<span translate>Statute</span>
</button>
<button mat-menu-item routerLink="comment-section">
<mat-icon>speaker_notes</mat-icon>
<span translate>Comment fields</span>
</button>
<button mat-menu-item routerLink="/tags" *osPerms="'core.can_manage_tags'">
<mat-icon>local_offer</mat-icon>
<span translate>Tags</span>
</button>
<button mat-menu-item (click)="csvExportMotionList()">
<mat-icon>archive</mat-icon>
<span translate>Export as CSV</span>
</button>
<button mat-menu-item routerLink="import">
<mat-icon>save_alt</mat-icon>
<span translate>Import</span><span>&nbsp;...</span>
</button>
</div>
</div>
<div *ngIf="isMultiSelect">
<button mat-menu-item (click)="selectAll()">
@ -175,7 +180,7 @@
<mat-icon>clear</mat-icon>
<span translate>Deselect all</span>
</button>
<div *osPerms="'motions.can_manage'">
<div *ngIf="perms.isAllowed('change_metadata')">
<mat-divider></mat-divider>
<button mat-menu-item (click)="multiselectWrapper(multiselectService.setStateOfMultiple(selectedRows))">
<mat-icon>label</mat-icon>
@ -217,15 +222,17 @@
<span translate>Move to agenda item</span>
</button>
</div>
<mat-divider></mat-divider>
<button
mat-menu-item
class="red-warning-text"
(click)="multiselectService.delete(selectedRows); toggleMultiSelect()"
>
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
<div *ngIf="perms.isAllowed('manage')">
<mat-divider></mat-divider>
<button
mat-menu-item
class="red-warning-text"
(click)="multiselectService.delete(selectedRows); toggleMultiSelect()"
>
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</div>
</div>
</mat-menu>
</mat-drawer-container>

View File

@ -3,22 +3,23 @@ import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
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 { 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 { MotionCsvExportService } from '../../services/motion-csv-export.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 { 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 { ViewWorkflow } from '../../models/view-workflow';
import { ViewCategory } from '../../models/view-category';
import { ViewMotionBlock } from '../../models/view-motion-block';
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
import { WorkflowRepositoryService } from '../../services/workflow-repository.service';
/**
@ -75,6 +76,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* @param userRepo
* @param sortService
* @param filterService
* @param perms LocalPermissionService
*/
public constructor(
titleService: Title,
@ -90,7 +92,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
private motionCsvExport: MotionCsvExportService,
public multiselectService: MotionMultiselectService,
public sortService: MotionSortListService,
public filterService: MotionFilterListService
public filterService: MotionFilterListService,
public perms: LocalPermissionsService
) {
super(titleService, translate, matSnackBar);

View File

@ -6,10 +6,13 @@
<div *ngIf="poll.has_votes" class="on-transition-fade poll-result">
<div *ngFor="let key of pollValues">
<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>
<span translate>{{ getLabel(key) }}</span>:&nbsp;{{ getNumber(key) }}
<span translate>{{ getLabel(key) }}</span
>:&nbsp;{{ getNumber(key) }}
<span *ngIf="!isAbstractValue(key)">({{ getPercent(key) }}%)</span>
</div>
<div *ngIf="!isAbstractValue(key)" class="poll-progress-bar">
@ -22,39 +25,53 @@
</div>
</div>
</div>
<hr *ngIf="key ==='abstain'" flex />
<hr *ngIf="key === 'abstain'" flex />
</div>
<!-- quorum -->
<div *ngIf="abstractPoll"><span translate>Quorum not calculable.</span></div>
<div class="poll-quorum-line" *ngIf="!abstractPoll">
<span>
<span *ngIf="yesQuorum">
<mat-icon color="warn" *ngIf="!quorumYesReached"> thumb_down </mat-icon>
<mat-icon color="primary" *ngIf="quorumYesReached"> thumb_up </mat-icon>
<div *osPerms="'motions.can_manage'">
<div *ngIf="abstractPoll"><span translate>Quorum not calculable.</span></div>
<div class="poll-quorum-line" *ngIf="!abstractPoll">
<span>
<span *ngIf="yesQuorum">
<mat-icon color="warn" *ngIf="!quorumYesReached"> thumb_down </mat-icon>
<mat-icon color="primary" *ngIf="quorumYesReached"> thumb_up </mat-icon>
</span>
<button mat-button [matMenuTriggerFor]="majorityMenu">
&nbsp;<span translate>{{ getQuorumLabel() }}</span> &nbsp;<span
*ngIf="majorityChoice !== 'disabled'"
>({{ yesQuorum }})</span
>
</button>
<span *ngIf="majorityChoice !== 'disabled'">
<span *ngIf="quorumYesReached" translate> reached.</span>
<span *ngIf="!quorumYesReached" translate> not reached.</span>
</span>
<span *ngIf="majorityChoice === 'disabled'"
>&nbsp;&mdash;&nbsp; <span translate>No quorum calculated</span>
</span>
</span>
<button mat-button [matMenuTriggerFor]="majorityMenu">
&nbsp;<span translate>{{ getQuorumLabel() }}</span>
&nbsp;<span *ngIf="majorityChoice !== 'disabled'">({{ yesQuorum }})</span>
</button>
<span *ngIf="majorityChoice !== 'disabled'">
<span *ngIf="quorumYesReached" translate> reached.</span>
<span *ngIf="!quorumYesReached" translate> not reached.</span>
</span>
<span *ngIf="majorityChoice === 'disabled'"
>&nbsp;&mdash;&nbsp; <span translate>No quorum calculated</span>
</span>
</span>
</div>
</div>
</div>
</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()">
<mat-icon inline>edit</mat-icon>
</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>
</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>
</button>
</ng-container>

View File

@ -2,49 +2,124 @@ import { Injectable } from '@angular/core';
import { OperatorService } from '../../../core/services/operator.service';
import { ViewMotion } from '../models/view-motion';
import { ConfigService } from '../../../core/services/config.service';
import { ConstantsService } from 'app/core/services/constants.service';
@Injectable({
providedIn: 'root'
})
export class LocalPermissionsService {
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
this.configService
.get('motions_min_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
* correct permission to perform the given action.
* Determine if the user (Operator) has the correct permission to perform the given action.
*
* actions might be:
* - create
* - support
* - unsupport
* - 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 motion the motion for which to perform the action
*/
public isAllowed(action: string, motion?: ViewMotion): boolean {
if (motion) {
switch (action) {
case 'support':
return (
this.operator.hasPerms('motions.can_support') &&
this.configMinSupporters > 0 &&
motion.state.allow_support &&
motion.submitters.indexOf(this.operator.user) === -1 &&
motion.supporters.indexOf(this.operator.user) === -1
);
case 'unsupport':
return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1;
case 'createpoll':
return this.operator.hasPerms('motions.can_manage') && motion.state.allow_create_poll;
default:
switch (action) {
case 'create':
return this.operator.hasPerms('motions.can_create');
case 'support':
if (!motion) {
return false;
}
}
return (
this.operator.hasPerms('motions.can_support') &&
this.configMinSupporters > 0 &&
motion.state.allow_support &&
motion.submitters.indexOf(this.operator.user) === -1 &&
motion.supporters.indexOf(this.operator.user) === -1
);
case 'unsupport':
if (!motion) {
return false;
}
return motion.state.allow_support && motion.supporters.indexOf(this.operator.user) !== -1;
case 'createpoll':
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:
return false;
}
}
}

View File

@ -692,4 +692,13 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
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;
}
}