From 64566b84b40bca26e9f35c83cd5caaf40a6d3022 Mon Sep 17 00:00:00 2001
From: Ruben Bimberg <ruben.bimberg@mni.thm.de>
Date: Sat, 18 Sep 2021 00:42:57 +0200
Subject: [PATCH] Fix profanity bugs and implement profanity testing for
 keywords

---
 .../profanity-settings.component.ts           | 79 ++++++++++---------
 .../services/util/profanity-filter.service.ts |  1 -
 src/app/services/util/room-data.service.ts    | 50 +++++++++---
 .../util/topic-cloud-admin.service.ts         |  6 +-
 4 files changed, 84 insertions(+), 52 deletions(-)

diff --git a/src/app/components/creator/_dialogs/profanity-settings/profanity-settings.component.ts b/src/app/components/creator/_dialogs/profanity-settings/profanity-settings.component.ts
index ad81ba4ea..43761cd16 100644
--- a/src/app/components/creator/_dialogs/profanity-settings/profanity-settings.component.ts
+++ b/src/app/components/creator/_dialogs/profanity-settings/profanity-settings.component.ts
@@ -1,22 +1,22 @@
-import {Component,Inject,OnInit} from '@angular/core';
-import {MAT_DIALOG_DATA,MatDialog,MatDialogRef} from '@angular/material/dialog';
-import {RoomCreatorPageComponent} from '../../room-creator-page/room-creator-page.component';
-import {NotificationService} from '../../../../services/util/notification.service';
-import {TranslateService} from '@ngx-translate/core';
-import {RoomService} from '../../../../services/http/room.service';
-import {Router} from '@angular/router';
-import {EventService} from '../../../../services/util/event.service';
-import {ProfanityFilter,Room} from '../../../../models/room';
+import { Component, Inject, OnInit } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { RoomCreatorPageComponent } from '../../room-creator-page/room-creator-page.component';
+import { NotificationService } from '../../../../services/util/notification.service';
+import { TranslateService } from '@ngx-translate/core';
+import { RoomService } from '../../../../services/http/room.service';
+import { Router } from '@angular/router';
+import { EventService } from '../../../../services/util/event.service';
+import { ProfanityFilter, Room } from '../../../../models/room';
 
 @Component({
-  selector:'app-profanity-settings',
-  templateUrl:'./profanity-settings.component.html',
-  styleUrls:['./profanity-settings.component.scss']
+  selector: 'app-profanity-settings',
+  templateUrl: './profanity-settings.component.html',
+  styleUrls: ['./profanity-settings.component.scss']
 })
-export class ProfanitySettingsComponent implements OnInit{
+export class ProfanitySettingsComponent implements OnInit {
 
   editRoom: Room;
-  check=false;
+  check = false;
   profanityCheck: boolean;
   censorPartialWordsCheck: boolean;
   censorLanguageSpecificCheck: boolean;
@@ -28,22 +28,22 @@ export class ProfanitySettingsComponent implements OnInit{
               protected roomService: RoomService,
               public router: Router,
               public eventService: EventService,
-              @Inject(MAT_DIALOG_DATA) public data: any){
+              @Inject(MAT_DIALOG_DATA) public data: any) {
   }
 
-  ngOnInit(){
-    this.profanityCheck=this.editRoom.profanityFilter!==ProfanityFilter.deactivated;
-    if(this.editRoom.profanityFilter===ProfanityFilter.all){
-      this.censorLanguageSpecificCheck=this.censorPartialWordsCheck=true;
-    }else if(this.profanityCheck){
-      this.censorLanguageSpecificCheck=this.editRoom.profanityFilter===ProfanityFilter.languageSpecific;
-      this.censorPartialWordsCheck=this.editRoom.profanityFilter===ProfanityFilter.partialWords;
+  ngOnInit() {
+    this.profanityCheck = this.editRoom.profanityFilter !== ProfanityFilter.deactivated;
+    if (this.editRoom.profanityFilter === ProfanityFilter.all) {
+      this.censorLanguageSpecificCheck = this.censorPartialWordsCheck = true;
+    } else if (this.profanityCheck) {
+      this.censorLanguageSpecificCheck = this.editRoom.profanityFilter === ProfanityFilter.languageSpecific;
+      this.censorPartialWordsCheck = this.editRoom.profanityFilter === ProfanityFilter.partialWords;
     }
   }
 
-  showMessage(label: string,event: boolean){
-    if(event){
-      this.translationService.get('room-page.'+label).subscribe(msg=>{
+  showMessage(label: string, event: boolean) {
+    if (event) {
+      this.translationService.get('room-page.' + label).subscribe(msg => {
         this.notificationService.show(msg);
       });
     }
@@ -53,32 +53,33 @@ export class ProfanitySettingsComponent implements OnInit{
   /**
    * Returns a lambda which closes the dialog on call.
    */
-  buildCloseDialogActionCallback(): () => void{
-    return ()=>this.closeDialog('abort');
+  buildCloseDialogActionCallback(): () => void {
+    return () => this.closeDialog('abort');
   }
 
   /**
    * Returns a lambda which executes the dialog dedicated action on call.
    */
-  buildSaveActionCallback(): () => void{
-    return ()=>this.save();
+  buildSaveActionCallback(): () => void {
+    return () => this.save();
   }
 
-  closeDialog(type: string): void{
+  closeDialog(type: string): void {
     this.dialogRef.close(type);
   }
 
-  save(): void{
-    this.editRoom.questionsBlocked=this.check;
-    this.editRoom.profanityFilter=this.profanityCheck?ProfanityFilter.none:ProfanityFilter.deactivated;
-    if(this.profanityCheck){
-      if(this.censorLanguageSpecificCheck&&this.censorPartialWordsCheck){
-        this.editRoom.profanityFilter=ProfanityFilter.all;
-      }else{
-        this.editRoom.profanityFilter=this.censorLanguageSpecificCheck?ProfanityFilter.languageSpecific:ProfanityFilter.none;
-        this.editRoom.profanityFilter=this.censorPartialWordsCheck?ProfanityFilter.partialWords:this.editRoom.profanityFilter;
+  save(): void {
+    this.editRoom.questionsBlocked = this.check;
+    this.editRoom.profanityFilter = this.profanityCheck ? ProfanityFilter.none : ProfanityFilter.deactivated;
+    if (this.profanityCheck) {
+      if (this.censorLanguageSpecificCheck && this.censorPartialWordsCheck) {
+        this.editRoom.profanityFilter = ProfanityFilter.all;
+      } else {
+        this.editRoom.profanityFilter = this.censorLanguageSpecificCheck ? ProfanityFilter.languageSpecific : ProfanityFilter.none;
+        this.editRoom.profanityFilter = this.censorPartialWordsCheck ? ProfanityFilter.partialWords : this.editRoom.profanityFilter;
       }
     }
+    this.roomService.updateRoom(this.editRoom).subscribe();
     this.closeDialog('update');
   }
 
diff --git a/src/app/services/util/profanity-filter.service.ts b/src/app/services/util/profanity-filter.service.ts
index e079bfe06..73045faea 100644
--- a/src/app/services/util/profanity-filter.service.ts
+++ b/src/app/services/util/profanity-filter.service.ts
@@ -74,7 +74,6 @@ export class ProfanityFilterService {
       profWords = this.profanityWords;
     }
     str = str.replace(new RegExp(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi), '');
-    // eslint-disable-next-line max-len
     const toCensoredString = censorPartialWordsCheck ? str.toLowerCase() : str.toLowerCase().split(/[\s,.]+/);
     profWords.concat(this.getProfanityListFromStorage()).forEach(word => {
       if (toCensoredString.includes(word)) {
diff --git a/src/app/services/util/room-data.service.ts b/src/app/services/util/room-data.service.ts
index 1530f4e58..54ccdc019 100644
--- a/src/app/services/util/room-data.service.ts
+++ b/src/app/services/util/room-data.service.ts
@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core';
-import { BehaviorSubject, Observable, of, Subject, Subscription } from 'rxjs';
+import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
 import { WsCommentService } from '../websockets/ws-comment.service';
 import { Message } from '@stomp/stompjs';
 import { Comment } from '../../models/comment';
@@ -9,6 +9,7 @@ import { RoomService } from '../http/room.service';
 import { ProfanityFilterService } from './profanity-filter.service';
 import { ProfanityFilter, Room } from '../../models/room';
 import { WsRoomService } from '../websockets/ws-room.service';
+import { SpacyKeyword } from '../http/spacy.service';
 
 export interface UpdateInformation {
   type: 'CommentCreated' | 'CommentPatched' | 'CommentHighlighted' | 'CommentDeleted';
@@ -81,6 +82,8 @@ interface FastRoomAccessObject {
   [commentId: string]: Comment;
 }
 
+type CommentFilterData = [body: string, genKeywords: SpacyKeyword[], userKeywords: SpacyKeyword[]];
+
 @Injectable({
   providedIn: 'root'
 })
@@ -92,8 +95,8 @@ export class RoomDataService {
   private _fastCommentAccess: FastRoomAccessObject = null;
   private _wsCommentServiceSubscription: Subscription = null;
   private _currentRoomId: string = null;
-  private _savedCommentsBeforeFilter = new Map();
-  private _savedCommentsAfterFilter = new Map();
+  private _savedCommentsBeforeFilter = new Map<string, CommentFilterData>();
+  private _savedCommentsAfterFilter = new Map<string, CommentFilterData>();
   private room: Room;
 
   constructor(private wsCommentService: WsCommentService,
@@ -103,6 +106,14 @@ export class RoomDataService {
               private wsRoomService: WsRoomService) {
   }
 
+  private static cloneKeywords(arr: SpacyKeyword[]) {
+    const newArr = [...arr];
+    for (let i = 0; i < newArr.length; i++) {
+      newArr[i] = { text: newArr[i].text, dep: [...newArr[i].dep] };
+    }
+    return newArr;
+  }
+
   get currentRoomData() {
     return this._currentComments;
   }
@@ -138,9 +149,9 @@ export class RoomDataService {
     const finish = new Subject<boolean>();
     const subscription = finish.asObservable().subscribe(_ => {
       if (this.room.profanityFilter !== ProfanityFilter.deactivated) {
-        comment.body = this._savedCommentsAfterFilter.get(comment.id);
+        [comment.body, comment.keywordsFromSpacy, comment.keywordsFromQuestioner] = this._savedCommentsAfterFilter.get(comment.id);
       } else {
-        comment.body = this._savedCommentsBeforeFilter.get(comment.id);
+        [comment.body, comment.keywordsFromSpacy, comment.keywordsFromQuestioner] = this._savedCommentsBeforeFilter.get(comment.id);
       }
       subscription.unsubscribe();
     });
@@ -162,30 +173,36 @@ export class RoomDataService {
   }
 
   getUnFilteredBody(id: string): string {
-    return this._savedCommentsBeforeFilter.get(id);
+    return this._savedCommentsBeforeFilter.get(id)[0];
   }
 
   getFilteredBody(id: string): string {
-    return this._savedCommentsAfterFilter.get(id);
+    return this._savedCommentsAfterFilter.get(id)[0];
   }
 
   private setCommentBody(comment: Comment) {
-    this._savedCommentsBeforeFilter.set(comment.id, comment.body);
+    const genKeywords = RoomDataService.cloneKeywords(comment.keywordsFromSpacy);
+    const userKeywords = RoomDataService.cloneKeywords(comment.keywordsFromQuestioner);
+    this._savedCommentsBeforeFilter.set(comment.id, [comment.body, genKeywords, userKeywords]);
     this._savedCommentsAfterFilter.set(comment.id, this.filterCommentOfProfanity(this.room, comment));
   }
 
   private filterAllCommentsBodies() {
     this._currentComments.forEach(comment => {
-      comment.body = this._savedCommentsBeforeFilter.get(comment.id);
+      [comment.body, comment.keywordsFromSpacy, comment.keywordsFromQuestioner] = this._savedCommentsBeforeFilter.get(comment.id);
       this.setCommentBody(comment);
       this.checkProfanity(comment);
     });
   }
 
-  private filterCommentOfProfanity(room: Room, comment: Comment): string {
+  private filterCommentOfProfanity(room: Room, comment: Comment): CommentFilterData {
     const partialWords = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.partialWords;
     const languageSpecific = room.profanityFilter === ProfanityFilter.all || room.profanityFilter === ProfanityFilter.languageSpecific;
-    return this.profanityFilterService.filterProfanityWords(comment.body, partialWords, languageSpecific, comment.language);
+    return [
+      this.profanityFilterService.filterProfanityWords(comment.body, partialWords, languageSpecific, comment.language),
+      this.checkKeywords(comment.keywordsFromSpacy, partialWords, languageSpecific, comment.language),
+      this.checkKeywords(comment.keywordsFromQuestioner, partialWords, languageSpecific, comment.language)
+    ];
   }
 
   private removeCommentBodies(key: string) {
@@ -422,4 +439,15 @@ export class RoomDataService {
     }
     this._fastCommentAccess[id] = undefined;
   }
+
+  private checkKeywords(keywords: SpacyKeyword[], partialWords: boolean, languageSpecific: boolean, lang: string): SpacyKeyword[] {
+    const newKeywords = [...keywords];
+    for (let i = 0; i < newKeywords.length; i++) {
+      newKeywords[i] = {
+        text: this.profanityFilterService.filterProfanityWords(newKeywords[i].text, partialWords, languageSpecific, lang),
+        dep: newKeywords[i].dep
+      };
+    }
+    return newKeywords;
+  }
 }
diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts
index 2da5fb97b..0e6c44fda 100644
--- a/src/app/services/util/topic-cloud-admin.service.ts
+++ b/src/app/services/util/topic-cloud-admin.service.ts
@@ -72,7 +72,10 @@ export class TopicCloudAdminService {
       if (wantedLabels && !keyword.dep.some(e => wantedLabels.includes(e))) {
         continue;
       }
-      let isProfanity = false;
+      let isProfanity = !!keyword.text.match(/\*/);
+      if (isProfanity) {
+        continue;
+      }
       const lowerCasedKeyword = keyword.text.toLowerCase();
       for (const word of config.blacklist) {
         if (lowerCasedKeyword.includes(word)) {
@@ -194,6 +197,7 @@ export class TopicCloudAdminService {
     if (updateRoom && userRole && userRole > UserRole.PARTICIPANT) {
       this.getRoom().subscribe(room => {
         room.blacklistIsActive = _adminData.blacklistIsActive;
+        room.profanityFilter = _adminData.profanityFilter;
         TopicCloudAdminService.applySettingsToRoom(room);
         this.updateRoom(room);
       });
-- 
GitLab