diff --git a/client/src/app/core/services/pdf-document.service.ts b/client/src/app/core/services/pdf-document.service.ts
index 6387fe248..a2ca65bb2 100644
--- a/client/src/app/core/services/pdf-document.service.ts
+++ b/client/src/app/core/services/pdf-document.service.ts
@@ -429,6 +429,19 @@ export class PdfDocumentService {
margin: [0, 10, 0, 0],
bold: true
},
+ userDataHeading: {
+ fontSize: 14,
+ margin: [0, 10],
+ bold: true
+ },
+ userDataTopic: {
+ fontSize: 12,
+ margin: [0, 5]
+ },
+ userDataValue: {
+ fontSize: 12,
+ margin: [15, 5]
+ },
tocEntry: {
fontSize: 12,
margin: [0, 0, 0, 0],
@@ -446,6 +459,15 @@ export class PdfDocumentService {
},
tocCategorySection: {
margin: [0, 0, 0, 10]
+ },
+ userDataTitle: {
+ fontSize: 26,
+ margin: [0, 0, 0, 0],
+ bold: true
+ },
+ tableHeader: {
+ bold: true,
+ fillColor: 'white'
}
};
}
diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.html b/client/src/app/site/users/components/user-detail/user-detail.component.html
index 7fa20b161..083149d96 100644
--- a/client/src/app/site/users/components/user-detail/user-detail.component.html
+++ b/client/src/app/site/users/components/user-detail/user-detail.component.html
@@ -29,6 +29,11 @@
security
Change password
+
+
diff --git a/client/src/app/site/users/components/user-detail/user-detail.component.ts b/client/src/app/site/users/components/user-detail/user-detail.component.ts
index 23e0c4c1a..5c20722ad 100644
--- a/client/src/app/site/users/components/user-detail/user-detail.component.ts
+++ b/client/src/app/site/users/components/user-detail/user-detail.component.ts
@@ -6,14 +6,15 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
-import { genders } from 'app/shared/models/users/user';
-import { ViewUser } from '../../models/view-user';
-import { UserRepositoryService } from '../../services/user-repository.service';
-import { Group } from '../../../../shared/models/users/group';
-import { DataStoreService } from '../../../../core/services/data-store.service';
-import { OperatorService } from '../../../../core/services/operator.service';
import { BaseViewComponent } from '../../../base/base-view';
+import { DataStoreService } from '../../../../core/services/data-store.service';
+import { genders } from 'app/shared/models/users/user';
+import { Group } from '../../../../shared/models/users/group';
+import { OperatorService } from '../../../../core/services/operator.service';
import { PromptService } from '../../../../core/services/prompt.service';
+import { UserPdfExportService } from '../../services/user-pdf-export.service';
+import { UserRepositoryService } from '../../services/user-repository.service';
+import { ViewUser } from '../../models/view-user';
/**
* Users detail component for both new and existing users
@@ -82,6 +83,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
* @param DS DataStoreService
* @param operator OperatorService
* @param promptService PromptService
+ * @param pdfService UserPdfExportService used for export to pdf
*/
public constructor(
title: Title,
@@ -93,7 +95,8 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
private repo: UserRepositoryService,
private DS: DataStoreService,
private operator: OperatorService,
- private promptService: PromptService
+ private promptService: PromptService,
+ private pdfService: UserPdfExportService
) {
super(title, translate, matSnackBar);
@@ -359,4 +362,11 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
}
});
}
+
+ /**
+ * Triggers the pdf download for this user
+ */
+ public onDownloadPdf(): void {
+ this.pdfService.exportSingleUserAccessPDF(this.user);
+ }
}
diff --git a/client/src/app/site/users/components/user-list/user-list.component.html b/client/src/app/site/users/components/user-list/user-list.component.html
index 59b0034e1..ceb673c74 100644
--- a/client/src/app/site/users/components/user-list/user-list.component.html
+++ b/client/src/app/site/users/components/user-list/user-list.component.html
@@ -97,12 +97,16 @@
Groups
-
-
-
+
+
+
+
+
-
diff --git a/client/src/app/site/users/components/user-list/user-list.component.ts b/client/src/app/site/users/components/user-list/user-list.component.ts
index 18a8b9160..0ca818a20 100644
--- a/client/src/app/site/users/components/user-list/user-list.component.ts
+++ b/client/src/app/site/users/components/user-list/user-list.component.ts
@@ -4,18 +4,19 @@ import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
-import { CsvExportService } from '../../../../core/services/csv-export.service';
import { ChoiceService } from '../../../../core/services/choice.service';
import { ConfigService } from 'app/core/services/config.service';
-import { ListViewBaseComponent } from '../../../base/list-view-base';
+import { CsvExportService } from '../../../../core/services/csv-export.service';
import { GroupRepositoryService } from '../../services/group-repository.service';
+import { ListViewBaseComponent } from '../../../base/list-view-base';
import { PromptService } from '../../../../core/services/prompt.service';
-import { UserRepositoryService } from '../../services/user-repository.service';
-import { ViewUser } from '../../models/view-user';
import { UserFilterListService } from '../../services/user-filter-list.service';
+import { UserRepositoryService } from '../../services/user-repository.service';
+import { UserPdfExportService } from '../../services/user-pdf-export.service';
import { UserSortListService } from '../../services/user-sort-list.service';
import { ViewportService } from '../../../../core/services/viewport.service';
import { OperatorService } from '../../../../core/services/operator.service';
+import { ViewUser } from '../../models/view-user';
/**
* Component for the user list view.
@@ -51,7 +52,6 @@ export class UserListComponent extends ListViewBaseComponent implement
}
/**
- * /**
* The usual constructor for components
* @param titleService Serivce for setting the title
* @param translate Service for translation handling
@@ -68,6 +68,7 @@ export class UserListComponent extends ListViewBaseComponent implement
* @param filterService
* @param sortService
* @param config ConfigService
+ * @param userPdf Service for downloading pdf
*/
public constructor(
titleService: Title,
@@ -84,7 +85,8 @@ export class UserListComponent extends ListViewBaseComponent implement
private promptService: PromptService,
public filterService: UserFilterListService,
public sortService: UserSortListService,
- config: ConfigService
+ config: ConfigService,
+ private userPdf: UserPdfExportService
) {
super(titleService, translate, matSnackBar);
@@ -128,7 +130,8 @@ export class UserListComponent extends ListViewBaseComponent implement
}
/**
- * Export all users as CSV
+ * Export all users currently matching the filter
+ * as CSV (including personal information such as initial passwords)
*/
public csvExportUserList(): void {
this.csvExport.export(
@@ -151,6 +154,22 @@ export class UserListComponent extends ListViewBaseComponent implement
);
}
+ /**
+ * Export all users currently matching the filter as PDF
+ * (access information, including personal information such as initial passwords)
+ */
+ public onDownloadAccessPdf(): void {
+ this.userPdf.exportMultipleUserAccessPDF(this.dataSource.data);
+ }
+
+ /**
+ * triggers the download of a simple participant list (no details on user name and passwords)
+ * with all users currently matching the filter
+ */
+ public pdfExportUserList(): void {
+ this.userPdf.exportUserList(this.dataSource.data);
+ }
+
/**
* Bulk deletes users. Needs multiSelect mode to fill selectedRows
*/
diff --git a/client/src/app/site/users/services/user-pdf-export.service.spec.ts b/client/src/app/site/users/services/user-pdf-export.service.spec.ts
new file mode 100644
index 000000000..97bdbb497
--- /dev/null
+++ b/client/src/app/site/users/services/user-pdf-export.service.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+
+import { UserPdfExportService } from './user-pdf-export.service';
+import { E2EImportsModule } from 'e2e-imports.module';
+
+describe('UserPdfExportService', () => {
+ beforeEach(() =>
+ TestBed.configureTestingModule({
+ imports: [E2EImportsModule]
+ })
+ );
+
+ it('should be created', () => {
+ const service: UserPdfExportService = TestBed.get(UserPdfExportService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/client/src/app/site/users/services/user-pdf-export.service.ts b/client/src/app/site/users/services/user-pdf-export.service.ts
new file mode 100644
index 000000000..29543b726
--- /dev/null
+++ b/client/src/app/site/users/services/user-pdf-export.service.ts
@@ -0,0 +1,73 @@
+import { Injectable } from '@angular/core';
+
+import { TranslateService } from '@ngx-translate/core';
+
+import { PdfDocumentService } from 'app/core/services/pdf-document.service';
+import { UserPdfService } from './user-pdf.service';
+import { ViewUser } from '../models/view-user';
+
+/**
+ * Export service to handle various kind of exporting necessities for participants.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class UserPdfExportService {
+ /**
+ * Constructor
+ *
+ * @param translate TranslateService - handle translations
+ * @param userPdfService UserPdfService - convert users to PDF
+ * @param pdfDocumentService PdfDocumentService Actual pdfmake functions and global doc definitions
+ */
+ public constructor(
+ private translate: TranslateService,
+ private userPdfService: UserPdfService,
+ private pdfDocumentService: PdfDocumentService
+ ) {}
+
+ /**
+ * Exports a single user with access information to PDF
+ *
+ * @param user The user to export
+ */
+ public exportSingleUserAccessPDF(user: ViewUser): void {
+ const doc = this.userPdfService.userAccessToDocDef(user);
+ const filename = `${this.translate.instant('User')} ${user.short_name}`;
+ const metadata = {
+ title: filename
+ };
+ this.pdfDocumentService.download(doc, filename, metadata);
+ }
+
+ /**
+ * Exports multiple users with access information to a collection of PDFs
+ *
+ * @param Users
+ */
+ public exportMultipleUserAccessPDF(users: ViewUser[]): void {
+ const doc: object[] = [];
+ users.forEach(user => {
+ doc.push(this.userPdfService.userAccessToDocDef(user));
+ doc.push({ text: '', pageBreak: 'after' });
+ });
+ const filename = this.translate.instant('User');
+ const metadata = {
+ title: filename
+ };
+ this.pdfDocumentService.download(doc, filename, metadata);
+ }
+
+ /**
+ * Export a participant list
+ * @param users: The users to appear on that list
+ *
+ */
+ public exportUserList(users: ViewUser[]): void {
+ const filename = this.translate.instant('List of participants');
+ const metadata = {
+ title: filename
+ };
+ this.pdfDocumentService.download(this.userPdfService.createUserListDocDef(users), filename, metadata);
+ }
+}
diff --git a/client/src/app/site/users/services/user-pdf.service.spec.ts b/client/src/app/site/users/services/user-pdf.service.spec.ts
new file mode 100644
index 000000000..60a3a76a4
--- /dev/null
+++ b/client/src/app/site/users/services/user-pdf.service.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+
+import { UserPdfService } from './user-pdf.service';
+import { E2EImportsModule } from 'e2e-imports.module';
+
+describe('UserPdfService', () => {
+ beforeEach(() =>
+ TestBed.configureTestingModule({
+ imports: [E2EImportsModule]
+ })
+ );
+
+ it('should be created', () => {
+ const service: UserPdfService = TestBed.get(UserPdfService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/client/src/app/site/users/services/user-pdf.service.ts b/client/src/app/site/users/services/user-pdf.service.ts
new file mode 100644
index 000000000..89712be49
--- /dev/null
+++ b/client/src/app/site/users/services/user-pdf.service.ts
@@ -0,0 +1,291 @@
+import { Injectable } from '@angular/core';
+
+import { TranslateService } from '@ngx-translate/core';
+
+import { ConfigService } from 'app/core/services/config.service';
+import { ViewUser } from '../models/view-user';
+
+/**
+ * Creates a pdf for a user, containing greetings and initial login information
+ * Provides the public methods `userAccessToDocDef(user: ViewUser)` and
+ * `createUserListDocDef(users:ViewUser[])` which should be convenient to use.
+ * @example
+ * ```ts
+ * const pdfMakeCompatibleDocDef = this.UserPdfService.userAccessToDocDef(User);
+ * const pdfMakeCompatibleDocDef = this.UserPdfService.createUserListDocDef(Users);
+ * ```
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class UserPdfService {
+ /**
+ * Constructor
+ *
+ * @param translate handle translations
+ * @param configService Read config variables
+ */
+ public constructor(private translate: TranslateService, private configService: ConfigService) {}
+
+ /**
+ * Converts a user to PdfMake doc definition, containing access information
+ * for login and wlan access, if applicable
+ *
+ * @returns doc def for the user
+ */
+ public userAccessToDocDef(user: ViewUser): object {
+ const userHeadline = [
+ {
+ text: user.short_name,
+ style: 'userDataTitle'
+ }
+ ];
+ if (user.structure_level) {
+ userHeadline.push({
+ text: user.structure_level,
+ style: 'userDataHeading'
+ });
+ }
+ return [userHeadline, this.createAccessDataContent(user), this.createWelcomeText()];
+ }
+
+ /**
+ * Generates the document definitions for a participant list with the given users, sorted as in the input.
+ *
+ * @param users An array of users
+ * @returns pdfMake definitions for a table including name, structure level and groups for each user
+ */
+ public createUserListDocDef(users: ViewUser[]): object {
+ const title = {
+ text: this.translate.instant('List of participants'),
+ style: 'title'
+ };
+ return [title, this.createUserList(users)];
+ }
+
+ /**
+ * Handles the creation of access data for the given user
+ *
+ * @param user
+ * @returns
+ */
+ private createAccessDataContent(user: ViewUser): object {
+ return {
+ columns: [this.createWifiAccessContent(), this.createUserAccessContent(user)],
+ margin: [0, 20]
+ };
+ }
+
+ /**
+ * Creates the wifi access data, including qr code, for the configured event wlan parameters
+ *
+ * @returns pdfMake definitions
+ */
+ private createWifiAccessContent(): object {
+ const wifiColumn: object[] = [
+ {
+ text: this.translate.instant('WLAN access data'),
+ style: 'userDataHeading'
+ },
+ {
+ text: this.translate.instant('WLAN name (SSID)') + ':',
+ style: 'userDataTopic'
+ },
+ {
+ text: this.configService.instant('users_pdf_wlan_ssid') || '-',
+ style: 'userDataValue'
+ },
+ {
+ text: this.translate.instant('WLAN password') + ':',
+ style: 'userDataTopic'
+ },
+ {
+ text: this.configService.instant('users_pdf_wlan_password') || '-',
+ style: 'userDataValue'
+ },
+ {
+ text: this.translate.instant('WLAN encryption') + ':',
+ style: 'userDataTopic'
+ },
+ {
+ text: this.configService.instant('users_pdf_wlan_encryption') || '-',
+ style: 'userDataValue'
+ },
+ {
+ text: '\n'
+ }
+ ];
+ if (
+ this.configService.instant('users_pdf_wlan_ssid') &&
+ this.configService.instant('users_pdf_wlan_encryption')
+ ) {
+ const wifiQrCode =
+ 'WIFI:S:' +
+ this.configService.instant('users_pdf_wlan_ssid') +
+ ';T:' +
+ this.configService.instant('users_pdf_wlan_encryption') +
+ ';P:' +
+ this.configService.instant('users_pdf_wlan_password') +
+ ';;';
+ wifiColumn.push(
+ {
+ qr: wifiQrCode,
+ fit: 120,
+ margin: [0, 0, 0, 8]
+ },
+ {
+ text: this.translate.instant('Scan this QR code to connect to WLAN.'),
+ style: 'small'
+ }
+ );
+ }
+ return wifiColumn;
+ }
+
+ /**
+ * Creates access information (login name, initial password) for the given user,
+ * additionally encoded in a qr code
+ *
+ * @param user
+ * @returns pdfMake definitions
+ */
+ private createUserAccessContent(user: ViewUser): object {
+ const columnOpenSlides: object[] = [
+ {
+ text: this.translate.instant('OpenSlides access data'),
+ style: 'userDataHeading'
+ },
+ {
+ text: this.translate.instant('Username') + ':',
+ style: 'userDataTopic'
+ },
+ {
+ text: user.username,
+ style: 'userDataValue'
+ },
+ {
+ text: this.translate.instant('Initial password') + ':',
+ style: 'userDataTopic'
+ },
+ {
+ text: user.default_password,
+ style: 'userDataValue'
+ },
+ {
+ text: 'URL:',
+ style: 'userDataTopic'
+ },
+ {
+ text: this.configService.instant('users_pdf_url') || '-',
+ link: this.configService.instant('users_pdf_url'),
+ style: 'userDataValue'
+ },
+ {
+ text: '\n'
+ }
+ ];
+ // url qr code
+ if (this.configService.instant('users_pdf_url')) {
+ columnOpenSlides.push(
+ {
+ qr: this.configService.instant('users_pdf_url'),
+ fit: 120,
+ margin: [0, 0, 0, 8]
+ },
+ {
+ text: this.translate.instant('Scan this QR code to open URL.'),
+ style: 'small'
+ }
+ );
+ }
+ return columnOpenSlides;
+ }
+
+ /**
+ * Generates a welcone text according to the events' configuration
+ *
+ * @returns pdfMake definitions
+ */
+ private createWelcomeText(): object {
+ return [
+ {
+ text: this.translate.instant(this.configService.instant('users_pdf_welcometitle')),
+ style: 'userDataHeading'
+ },
+ {
+ text: this.translate.instant(this.configService.instant('users_pdf_welcometext')),
+ style: 'userDataTopic'
+ }
+ ];
+ }
+
+ /**
+ * Handles the creation of the participant lists' table structure
+ *
+ * @param users: passed through to getListUsers
+ * @returns a pdfMake table definition ready to be put into a page
+ */
+ private createUserList(users: ViewUser[]): object {
+ const userTableBody: object[] = [
+ [
+ {
+ text: '#',
+ style: 'tableHeader'
+ },
+ {
+ text: this.translate.instant('Name'),
+ style: 'tableHeader'
+ },
+ {
+ text: this.translate.instant('Structure level'),
+ style: 'tableHeader'
+ },
+ {
+ text: this.translate.instant('Groups'),
+ style: 'tableHeader'
+ }
+ ]
+ ];
+ return {
+ table: {
+ widths: ['auto', '*', 'auto', 'auto'],
+ headerRows: 1,
+ body: userTableBody.concat(this.getListUsers(users))
+ },
+ layout: {
+ hLineWidth: rowIndex => {
+ return rowIndex === 1;
+ },
+ vLineWidth: () => {
+ return 0;
+ },
+ fillColor: rowIndex => {
+ return rowIndex % 2 === 0 ? '#EEEEEE' : null;
+ }
+ }
+ };
+ }
+
+ /**
+ * parses the incoming users and generates pdfmake table lines for each of them
+ *
+ * @param users: The users to include, in order
+ * @returns column definitions with odd and even entries styled differently,
+ * short name, structure level and group name columns
+ */
+ private getListUsers(users: ViewUser[]): object[] {
+ const result = [];
+ let counter = 1;
+ users.forEach(user => {
+ const groupList = user.groups.map(grp => this.translate.instant(grp.name));
+ result.push([
+ { text: '' + counter },
+ { text: user.short_name },
+ { text: user.structure_level },
+ { text: groupList.join(', ') }
+ ]);
+ counter += 1;
+ });
+ return result;
+ }
+}