Skip to content

Commit b837f63

Browse files
committed
119602: Add 'Accessibility Settings' Klaro option & respect user choice
1 parent e838873 commit b837f63

5 files changed

Lines changed: 113 additions & 37 deletions

File tree

src/app/accessibility/accessibility-settings.service.ts

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@angular/core';
2-
import { Observable, of, switchMap } from 'rxjs';
2+
import { Observable, of, switchMap, combineLatest } from 'rxjs';
33
import { map, take } from 'rxjs/operators';
44
import { CookieService } from '../core/services/cookie.service';
55
import { hasValue, isNotEmpty, hasNoValue } from '../shared/empty.util';
@@ -10,6 +10,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
1010
import cloneDeep from 'lodash/cloneDeep';
1111
import { environment } from '../../environments/environment';
1212
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
13+
import { KlaroService } from '../shared/cookies/klaro.service';
1314

1415
/**
1516
* Name of the cookie used to store the settings locally
@@ -62,6 +63,7 @@ export class AccessibilitySettingsService {
6263
protected cookieService: CookieService,
6364
protected authService: AuthService,
6465
protected ePersonService: EPersonDataService,
66+
protected klaroService: KlaroService,
6567
) {
6668
}
6769

@@ -125,8 +127,9 @@ export class AccessibilitySettingsService {
125127
*
126128
* Returns 'cookie' when the changes were stored in the cookie.
127129
* Returns 'metadata' when the changes were stored in metadata.
130+
* Returns 'failed' when both options failed.
128131
*/
129-
set(setting: AccessibilitySetting, value: string): Observable<'cookie' | 'metadata'> {
132+
set(setting: AccessibilitySetting, value: string): Observable<'metadata' | 'cookie' | 'failed'> {
130133
return this.updateSettings({ [setting]: value });
131134
}
132135

@@ -137,18 +140,15 @@ export class AccessibilitySettingsService {
137140
*
138141
* Returns 'cookie' when the changes were stored in the cookie.
139142
* Returns 'metadata' when the changes were stored in metadata.
143+
* Returns 'failed' when both options failed.
140144
*/
141-
setSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
145+
setSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
142146
return this.setSettingsInAuthenticatedUserMetadata(settings).pipe(
143147
take(1),
144-
map((succeeded) => {
145-
if (!succeeded) {
146-
this.setSettingsInCookie(settings);
147-
return 'cookie';
148-
} else {
149-
return 'metadata';
150-
}
151-
})
148+
map(saveLocation => saveLocation === 'metadata'),
149+
switchMap((savedInMetadata) =>
150+
savedInMetadata ? ofMetadata() : this.setSettingsInCookie(settings)
151+
),
152152
);
153153
}
154154

@@ -158,8 +158,9 @@ export class AccessibilitySettingsService {
158158
*
159159
* Returns 'cookie' when the changes were stored in the cookie.
160160
* Returns 'metadata' when the changes were stored in metadata.
161+
* Returns 'failed' when both options failed.
161162
*/
162-
updateSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
163+
updateSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
163164
return this.getAll().pipe(
164165
take(1),
165166
map(currentSettings => Object.assign({}, currentSettings, settings)),
@@ -170,9 +171,9 @@ export class AccessibilitySettingsService {
170171
/**
171172
* Attempts to set the provided settings on the currently authorized user's metadata.
172173
* Emits false when no user is authenticated or when the metadata update failed.
173-
* Emits true when the metadata update succeeded.
174+
* Emits 'metadata' when the metadata update succeeded, and 'failed' otherwise.
174175
*/
175-
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<boolean> {
176+
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<'metadata' | 'failed'> {
176177
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
177178
take(1),
178179
switchMap(user => {
@@ -181,7 +182,7 @@ export class AccessibilitySettingsService {
181182
const clonedUser = cloneDeep(user);
182183
return this.setSettingsInMetadata(clonedUser, settings);
183184
} else {
184-
return of(false);
185+
return ofFailed();
185186
}
186187
})
187188
);
@@ -194,7 +195,7 @@ export class AccessibilitySettingsService {
194195
setSettingsInMetadata(
195196
user: EPerson,
196197
settings: AccessibilitySettings,
197-
): Observable<boolean> {
198+
): Observable<'metadata' | 'failed'> {
198199
if (isNotEmpty(settings)) {
199200
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
200201
} else {
@@ -206,28 +207,42 @@ export class AccessibilitySettingsService {
206207
switchMap(operations =>
207208
isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
208209
getFirstCompletedRemoteData(),
209-
map(rd => rd.hasSucceeded),
210+
switchMap(rd => rd.hasSucceeded ? ofMetadata() : ofFailed()),
210211
);
211212
}
212213

213214
/**
214-
* Sets the provided settings in a cookie
215+
* Attempts to set the provided settings in a cookie.
216+
* Emits 'failed' when setting in a cookie failed due to the cookie not being accepted, 'cookie' when it succeeded.
215217
*/
216-
setSettingsInCookie(settings: AccessibilitySettings) {
217-
if (isNotEmpty(settings)) {
218-
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
219-
} else {
220-
this.cookieService.remove(ACCESSIBILITY_COOKIE);
221-
}
218+
setSettingsInCookie(settings: AccessibilitySettings): Observable<'cookie' | 'failed'> {
219+
return this.klaroService.getSavedPreferences().pipe(
220+
map(preferences => preferences.accessibility),
221+
map((accessibilityCookieAccepted: boolean) => {
222+
if (accessibilityCookieAccepted) {
223+
if (isNotEmpty(settings)) {
224+
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
225+
} else {
226+
this.cookieService.remove(ACCESSIBILITY_COOKIE);
227+
}
228+
229+
return 'cookie';
230+
} else {
231+
return 'failed';
232+
}
233+
}),
234+
);
222235
}
223236

224237
/**
225238
* Clears all settings in the cookie and attempts to clear settings in metadata.
226-
* Emits true if settings in metadata were cleared and false otherwise.
239+
* Emits an array mentioning which settings succeeded or failed.
227240
*/
228-
clearSettings(): Observable<boolean> {
229-
this.setSettingsInCookie({});
230-
return this.setSettingsInAuthenticatedUserMetadata({});
241+
clearSettings(): Observable<['cookie' | 'failed', 'metadata' | 'failed']> {
242+
return combineLatest([
243+
this.setSettingsInCookie({}),
244+
this.setSettingsInAuthenticatedUserMetadata({}),
245+
]);
231246
}
232247

233248
/**
@@ -323,3 +338,11 @@ function millisecondsToSeconds(millisecondsStr: string): string {
323338
return (milliseconds / 1000).toString();
324339
}
325340
}
341+
342+
function ofMetadata(): Observable<'metadata'> {
343+
return of('metadata');
344+
}
345+
346+
function ofFailed(): Observable<'failed'> {
347+
return of('failed');
348+
}

src/app/info/accessibility-settings/accessibility-settings.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ <h2>{{ 'info.accessibility-settings.title' | translate }}</h2>
8787

8888
</form>
8989

90+
<div *ngIf="(isAuthenticated | async) === false && (cookieIsAccepted | async) === false" class="mt-2">
91+
<ds-alert [type]="AlertType.Warning">{{ 'info.accessibility-settings.cookie-warning' | translate }}</ds-alert>
92+
</div>
93+
9094
</div>

src/app/info/accessibility-settings/accessibility-settings.component.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, OnDestroy, OnInit } from '@angular/core';
22
import { AuthService } from '../../core/auth/auth.service';
33
import {
44
AccessibilitySetting,
55
AccessibilitySettingsService,
66
AccessibilitySettingsFormValues,
77
} from '../../accessibility/accessibility-settings.service';
8-
import { take } from 'rxjs';
8+
import { BehaviorSubject, distinctUntilChanged, map, Subscription, take } from 'rxjs';
99
import { NotificationsService } from '../../shared/notifications/notifications.service';
1010
import { TranslateService } from '@ngx-translate/core';
1111
import { isEmpty } from 'src/app/shared/empty.util';
12+
import { AlertType } from '../../shared/alert/aletr-type';
13+
import { KlaroService } from '../../shared/cookies/klaro.service';
1214

1315
/**
1416
* Component providing the form where users can update accessibility settings.
@@ -17,20 +19,41 @@ import { isEmpty } from 'src/app/shared/empty.util';
1719
selector: 'ds-accessibility-settings',
1820
templateUrl: './accessibility-settings.component.html'
1921
})
20-
export class AccessibilitySettingsComponent implements OnInit {
22+
export class AccessibilitySettingsComponent implements OnInit, OnDestroy {
23+
// Redeclared for use in template
24+
protected readonly AlertType = AlertType;
2125

2226
protected formValues: AccessibilitySettingsFormValues;
2327

28+
isAuthenticated: BehaviorSubject<boolean> = new BehaviorSubject(false);
29+
cookieIsAccepted: BehaviorSubject<boolean> = new BehaviorSubject(false);
30+
31+
private subscriptions: Subscription[] = [];
32+
2433
constructor(
2534
protected authService: AuthService,
2635
protected settingsService: AccessibilitySettingsService,
2736
protected notificationsService: NotificationsService,
2837
protected translateService: TranslateService,
38+
protected klaroService: KlaroService,
2939
) {
3040
}
3141

3242
ngOnInit() {
3343
this.updateFormValues();
44+
45+
this.subscriptions.push(
46+
this.authService.isAuthenticated().pipe(distinctUntilChanged())
47+
.subscribe(val => this.isAuthenticated.next(val)),
48+
this.klaroService.getSavedPreferences().pipe(
49+
map(preferences => preferences?.accessibility === true),
50+
distinctUntilChanged(),
51+
).subscribe(val => this.cookieIsAccepted.next(val)),
52+
);
53+
}
54+
55+
ngOnDestroy() {
56+
this.subscriptions.forEach(subscription => subscription.unsubscribe());
3457
}
3558

3659
/**
@@ -42,8 +65,12 @@ export class AccessibilitySettingsComponent implements OnInit {
4265

4366
if (this.settingsService.allValid(convertedValues)) {
4467
this.settingsService.setSettings(convertedValues).pipe(take(1)).subscribe(location => {
45-
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
46-
this.updateFormValues();
68+
if (location !== 'failed') {
69+
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
70+
this.updateFormValues();
71+
} else {
72+
this.notificationsService.error(null, this.translateService.instant('info.accessibility-settings.failed-notification'));
73+
}
4774
});
4875
} else {
4976
this.notificationsService.error(
@@ -78,9 +105,13 @@ export class AccessibilitySettingsComponent implements OnInit {
78105
* Resets accessibility settings
79106
*/
80107
resetSettings() {
81-
this.settingsService.clearSettings().pipe(take(1)).subscribe(() => {
82-
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.reset-notification'));
83-
this.updateFormValues();
108+
this.settingsService.clearSettings().pipe(take(1)).subscribe(([cookieReset, metadataReset]) => {
109+
if (cookieReset === 'failed' && metadataReset === 'failed') {
110+
this.notificationsService.warning(null, this.translateService.instant('info.accessibility-settings.reset-failed'));
111+
} else {
112+
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.reset-notification'));
113+
this.updateFormValues();
114+
}
84115
});
85116
}
86117

src/app/shared/cookies/klaro-configuration.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TOKENITEM } from '../../core/auth/models/auth-token-info.model';
22
import { IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service';
33
import { LANG_COOKIE } from '../../core/locale/locale.service';
44
import { CAPTCHA_COOKIE, CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.service';
5+
import { ACCESSIBILITY_COOKIE } from '../../accessibility/accessibility-settings.service';
56

67
/**
78
* Cookie for has_agreed_end_user
@@ -189,6 +190,13 @@ export const klaroConfiguration: any = {
189190
onAccept: `window.refreshCaptchaScript?.call()`,
190191
onDecline: `window.refreshCaptchaScript?.call()`,
191192
onlyOnce: true,
192-
}
193+
},
194+
{
195+
name: 'accessibility',
196+
purposes: ['functional'],
197+
required: false,
198+
cookies: [ACCESSIBILITY_COOKIE],
199+
onlyOnce: false,
200+
},
193201
],
194202
};

src/assets/i18n/en.json5

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,10 @@
13501350

13511351
"cookies.consent.content-modal.service": "service",
13521352

1353+
"cookies.consent.app.title.accessibility": "Accessibility Settings",
1354+
1355+
"cookies.consent.app.description.accessibility": "Required for saving your accessibility settings locally",
1356+
13531357
"cookies.consent.app.title.authentication": "Authentication",
13541358

13551359
"cookies.consent.app.description.authentication": "Required for signing you in",
@@ -1844,10 +1848,14 @@
18441848

18451849
"info.accessibility-settings.breadcrumbs": "Accessibility settings",
18461850

1851+
"info.accessibility-settings.cookie-warning": "Saving the accessibility settings is currently not possible. Either log in to save the settings in user data, or accept the 'Accessibility Settings' cookie using the 'Cookie Settings' menu at the bottom of the page. Once the cookie has been accepted, you can reload the page to remove this message.",
1852+
18471853
"info.accessibility-settings.disableNotificationTimeOut.label": "Automatically close notifications after time out",
18481854

18491855
"info.accessibility-settings.disableNotificationTimeOut.hint": "When this toggle is activated, notifications will close automatically after the time out passes. When deactivated, notifications will remain open untill manually closed.",
18501856

1857+
"info.accessibility-settings.failed-notification": "Failed to save accessibility settings",
1858+
18511859
"info.accessibility-settings.invalid-form-notification": "Did not save. The form contains invalid values.",
18521860

18531861
"info.accessibility-settings.liveRegionTimeOut.label": "ARIA Live region time out (in seconds)",
@@ -1866,6 +1874,8 @@
18661874

18671875
"info.accessibility-settings.save-notification.metadata": "Successfully saved settings on the user profile.",
18681876

1877+
"info.accessibility-settings.reset-failed": "Failed to reset. Either log in or accept the 'Accessibility Settings' cookie.",
1878+
18691879
"info.accessibility-settings.reset-notification": "Successfully reset settings.",
18701880

18711881
"info.accessibility-settings.reset": "Reset accessibility settings",

0 commit comments

Comments
 (0)