diff --git a/src/app/core/data/item-request-data.service.spec.ts b/src/app/core/data/item-request-data.service.spec.ts index a5d18725109..0e0d9a1063a 100644 --- a/src/app/core/data/item-request-data.service.spec.ts +++ b/src/app/core/data/item-request-data.service.spec.ts @@ -8,6 +8,8 @@ import { ItemRequest } from '../shared/item-request.model'; import { PostRequest } from './request.models'; import { RequestCopyEmail } from '../../request-copy/email-request-copy/request-copy-email.model'; import { RestRequestMethod } from './rest-request-method'; +import { HttpHeaders } from '@angular/common/http'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; describe('ItemRequestDataService', () => { let service: ItemRequestDataService; @@ -40,8 +42,11 @@ describe('ItemRequestDataService', () => { describe('requestACopy', () => { it('should send a POST request containing the provided item request', (done) => { + let headers = new HttpHeaders(); + const options: HttpOptions = Object.create({}); + options.headers = headers; service.requestACopy(itemRequest).subscribe(() => { - expect(requestService.send).toHaveBeenCalledWith(new PostRequest(requestId, restApiEndpoint, itemRequest)); + expect(requestService.send).toHaveBeenCalledWith(new PostRequest(requestId, restApiEndpoint, itemRequest,options)); done(); }); }); diff --git a/src/app/core/data/item-request-data.service.ts b/src/app/core/data/item-request-data.service.ts index ff6025f7ac8..0bc219462df 100644 --- a/src/app/core/data/item-request-data.service.ts +++ b/src/app/core/data/item-request-data.service.ts @@ -50,19 +50,22 @@ export class ItemRequestDataService extends IdentifiableDataService * Request a copy of an item * @param itemRequest */ - requestACopy(itemRequest: ItemRequest): Observable> { + requestACopy(itemRequest: ItemRequest, captchaToken: string = null): Observable> { const requestId = this.requestService.generateRequestId(); - const href$ = this.getItemRequestEndpoint(); - + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + if (captchaToken) { + headers = headers.append('x-recaptcha-token', captchaToken); + } + options.headers = headers; href$.pipe( find((href: string) => hasValue(href)), map((href: string) => { - const request = new PostRequest(requestId, href, itemRequest); + const request = new PostRequest(requestId, href, itemRequest,options); this.requestService.send(request); }) ).subscribe(); - return this.rdbService.buildFromRequestUUID(requestId).pipe( getFirstCompletedRemoteData() ); diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html index 2d4ac89fcc3..f7b5433cb7b 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html @@ -6,7 +6,8 @@

{{'bitstream-request-a-copy.header' | translate}}

{{'bitstream-request-a-copy.intro' | translate}} {{itemName}}

-

{{'bitstream-request-a-copy.intro.bitstream.one' | translate}} {{bitstreamName}}

+

{{'bitstream-request-a-copy.intro.bitstream.one' + | translate}} {{bitstreamName}}

{{'bitstream-request-a-copy.intro.bitstream.all' | translate}}

@@ -16,27 +17,24 @@

{{'bitstream-request-a-copy.header' | translate}}

-
- - {{ 'bitstream-request-a-copy.name.error' | translate }} - + type="text" id="name" formControlName="name" /> +
+ + {{ 'bitstream-request-a-copy.name.error' | translate }} +
- + -
- - {{ 'bitstream-request-a-copy.email.error' | translate }} - + [className]="(email.invalid) && (email.dirty || email.touched) ? 'form-control is-invalid' :'form-control'" + id="email" formControlName="email" /> +
+ + {{ 'bitstream-request-a-copy.email.error' | translate }} +
{{'bitstream-request-a-copy.email.hint' |translate}}
@@ -45,31 +43,41 @@

{{'bitstream-request-a-copy.header' | translate}}

{{'bitstream-request-a-copy.allfiles.label' |translate}}
- + + for="allfiles-true">{{'bitstream-request-a-copy.files-all-true.label' | translate}}
- + + for="allfiles-false">{{'bitstream-request-a-copy.files-all-false.label' | + translate}}
- - + +
+
- +
@@ -77,11 +85,10 @@

{{'bitstream-request-a-copy.header' | translate}}

{{'bitstream-request-a-copy.return' | translate}} - + [disabled]="!requestCopyVerification ? requestCopyForm.invalid : requestCopyForm.invalid || requestCopyVerification && !isRecaptchaCookieAccepted() || disableUntilChecked" + class="btn btn-default btn-primary" (click)="onSubmit()">{{'bitstream-request-a-copy.submit' | + translate}}
diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts index cbfbdf361f4..d0adeb04172 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { AuthService } from '../../../core/auth/auth.service'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, of } from 'rxjs'; import { Bitstream } from '../../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { @@ -25,6 +25,10 @@ import { EPerson } from '../../../core/eperson/models/eperson.model'; import { ItemRequest } from '../../../core/shared/item-request.model'; import { Location } from '@angular/common'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { CookieService } from 'src/app/core/services/cookie.service'; +import { CookieServiceMock } from 'src/app/shared/mocks/cookie.service.mock'; +import { GoogleRecaptchaService } from 'src/app/core/google-recaptcha/google-recaptcha.service'; +import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service'; describe('BitstreamRequestACopyPageComponent', () => { @@ -44,12 +48,29 @@ describe('BitstreamRequestACopyPageComponent', () => { let bitstream: Bitstream; let eperson; + + const captchaVersion$ = of('v3'); + const captchaMode$ = of('invisible'); + const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['true'] }); + const confResponseDisabled$ = createSuccessfulRemoteDataObject$({ values: ['false'] }); + + const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { + getRecaptchaToken: Promise.resolve('googleRecaptchaToken'), + executeRecaptcha: Promise.resolve('googleRecaptchaToken'), + getRecaptchaTokenResponse: Promise.resolve('googleRecaptchaToken'), + captchaVersion: captchaVersion$, + captchaMode: captchaMode$, + }); + let configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); + function init() { eperson = Object.assign(new EPerson(), { email: 'test@mail.org', metadata: { - 'eperson.firstname': [{value: 'Test'}], - 'eperson.lastname': [{value: 'User'}], + 'eperson.firstname': [{ value: 'Test' }], + 'eperson.lastname': [{ value: 'User' }], } }); authService = jasmine.createSpyObj('authService', { @@ -70,13 +91,13 @@ describe('BitstreamRequestACopyPageComponent', () => { notificationsService = new NotificationsServiceStub(); - item = Object.assign(new Item(), {uuid: 'item-uuid'}); + item = Object.assign(new Item(), { uuid: 'item-uuid' }); bitstream = Object.assign(new Bitstream(), { uuid: 'bitstreamUuid', _links: { - content: {href: 'bitstream-content-link'}, - self: {href: 'bitstream-self-link'}, + content: { href: 'bitstream-content-link' }, + self: { href: 'bitstream-self-link' }, } }); @@ -87,7 +108,7 @@ describe('BitstreamRequestACopyPageComponent', () => { ) }), queryParams: observableOf({ - bitstream : bitstream.uuid + bitstream: bitstream.uuid }) }; @@ -96,6 +117,9 @@ describe('BitstreamRequestACopyPageComponent', () => { }); router = new RouterStub(); + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); } function initTestbed() { @@ -103,15 +127,19 @@ describe('BitstreamRequestACopyPageComponent', () => { imports: [CommonModule, TranslateModule.forRoot(), FormsModule, ReactiveFormsModule], declarations: [BitstreamRequestACopyPageComponent], providers: [ - {provide: Location, useValue: location}, - {provide: ActivatedRoute, useValue: activatedRoute}, - {provide: Router, useValue: router}, - {provide: AuthorizationDataService, useValue: authorizationService}, - {provide: AuthService, useValue: authService}, - {provide: ItemRequestDataService, useValue: itemRequestDataService}, - {provide: NotificationsService, useValue: notificationsService}, - {provide: DSONameService, useValue: new DSONameServiceMock()}, - {provide: BitstreamDataService, useValue: bitstreamDataService}, + { provide: Location, useValue: location }, + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: Router, useValue: router }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: AuthService, useValue: authService }, + { provide: ItemRequestDataService, useValue: itemRequestDataService }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: DSONameService, useValue: new DSONameServiceMock() }, + { provide: BitstreamDataService, useValue: bitstreamDataService }, + { provide: CookieService, useValue: new CookieServiceMock() }, + { provide: GoogleRecaptchaService, useValue: googleRecaptchaService }, + { provide: ConfigurationDataService, useValue: configurationDataService }, + ] }) .compileComponents(); @@ -120,11 +148,14 @@ describe('BitstreamRequestACopyPageComponent', () => { describe('init', () => { beforeEach(waitForAsync(() => { init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); initTestbed(); })); beforeEach(() => { fixture = TestBed.createComponent(BitstreamRequestACopyPageComponent); component = fixture.componentInstance; + googleRecaptchaService.captchaVersion$ = captchaVersion$; + googleRecaptchaService.captchaMode$ = captchaMode$; fixture.detectChanges(); }); it('should init the comp', () => { @@ -136,6 +167,7 @@ describe('BitstreamRequestACopyPageComponent', () => { describe('when the user is not logged in', () => { beforeEach(waitForAsync(() => { init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); initTestbed(); })); beforeEach(() => { @@ -155,6 +187,7 @@ describe('BitstreamRequestACopyPageComponent', () => { beforeEach(waitForAsync(() => { init(); (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(true)); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); initTestbed(); })); beforeEach(() => { @@ -173,6 +206,7 @@ describe('BitstreamRequestACopyPageComponent', () => { describe('when no bitstream was provided', () => { beforeEach(waitForAsync(() => { init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); activatedRoute = { data: observableOf({ dso: createSuccessfulRemoteDataObject( @@ -204,6 +238,7 @@ describe('BitstreamRequestACopyPageComponent', () => { beforeEach(waitForAsync(() => { init(); (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(true)); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); initTestbed(); })); beforeEach(() => { @@ -222,6 +257,7 @@ describe('BitstreamRequestACopyPageComponent', () => { describe('onSuccess', () => { beforeEach(waitForAsync(() => { init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); initTestbed(); })); beforeEach(() => { @@ -234,7 +270,7 @@ describe('BitstreamRequestACopyPageComponent', () => { component.email.patchValue('user@name.org'); component.allfiles.patchValue('false'); component.message.patchValue('I would like to request a copy'); - + component.captchaToken = ''; component.onSubmit(); const itemRequest = Object.assign(new ItemRequest(), { @@ -246,7 +282,7 @@ describe('BitstreamRequestACopyPageComponent', () => { requestMessage: 'I would like to request a copy' }); - expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest); + expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest, component.captchaToken); expect(notificationsService.success).toHaveBeenCalled(); expect(location.back).toHaveBeenCalled(); }); @@ -255,6 +291,7 @@ describe('BitstreamRequestACopyPageComponent', () => { describe('onFail', () => { beforeEach(waitForAsync(() => { init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); (itemRequestDataService.requestACopy as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$()); initTestbed(); })); @@ -268,7 +305,78 @@ describe('BitstreamRequestACopyPageComponent', () => { component.email.patchValue('user@name.org'); component.allfiles.patchValue('false'); component.message.patchValue('I would like to request a copy'); + component.captchaToken = ''; + component.onSubmit(); + const itemRequest = Object.assign(new ItemRequest(), + { + itemId: item.uuid, + bitstreamId: bitstream.uuid, + allfiles: 'false', + requestEmail: 'user@name.org', + requestName: 'User Name', + requestMessage: 'I would like to request a copy' + }); + expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest, component.captchaToken); + expect(notificationsService.error).toHaveBeenCalled(); + expect(location.back).not.toHaveBeenCalled(); + }); + }); + }); + + describe('register with google recaptcha', () => { + describe('onSuccess', () => { + beforeEach(waitForAsync(() => { + init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamRequestACopyPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should send a registration to the service and on success display a message and return to home', () => { + component.name.patchValue('User Name'); + component.email.patchValue('user@name.org'); + component.allfiles.patchValue('false'); + component.message.patchValue('I would like to request a copy'); + component.captchaToken = 'googleRecaptchaToken'; + component.onSubmit(); + const itemRequest = Object.assign(new ItemRequest(), + { + itemId: item.uuid, + bitstreamId: bitstream.uuid, + allfiles: 'false', + requestEmail: 'user@name.org', + requestName: 'User Name', + requestMessage: 'I would like to request a copy' + }); + + expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest, component.captchaToken); + expect(notificationsService.success).toHaveBeenCalled(); + expect(location.back).toHaveBeenCalled(); + }); + }); + + describe('onFail', () => { + beforeEach(waitForAsync(() => { + init(); + (configurationDataService.findByPropertyName as jasmine.Spy).and.returnValue(confResponse$); + (itemRequestDataService.requestACopy as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$()); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamRequestACopyPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should send a registration to the service and on error display a message', () => { + component.name.patchValue('User Name'); + component.email.patchValue('user@name.org'); + component.allfiles.patchValue('false'); + component.message.patchValue('I would like to request a copy'); + component.captchaToken = ''; component.onSubmit(); const itemRequest = Object.assign(new ItemRequest(), { @@ -280,7 +388,7 @@ describe('BitstreamRequestACopyPageComponent', () => { requestMessage: 'I would like to request a copy' }); - expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest); + expect(itemRequestDataService.requestACopy).toHaveBeenCalledWith(itemRequest, component.captchaToken); expect(notificationsService.error).toHaveBeenCalled(); expect(location.back).not.toHaveBeenCalled(); }); diff --git a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts index 77e1049d87f..1f6ece5fd0b 100644 --- a/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts @@ -1,5 +1,5 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Optional } from '@angular/core'; +import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; @@ -7,7 +7,7 @@ import { Bitstream } from '../../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { AuthService } from '../../../core/auth/auth.service'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription, combineLatest, of, BehaviorSubject } from 'rxjs'; import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../../app-routing-paths'; import { TranslateService } from '@ngx-translate/core'; import { EPerson } from '../../../core/eperson/models/eperson.model'; @@ -20,6 +20,13 @@ import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { Location } from '@angular/common'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; import { getItemPageRoute } from '../../item-page-routing-paths'; +import { CookieService } from 'src/app/core/services/cookie.service'; +import { CAPTCHA_NAME, GoogleRecaptchaService } from 'src/app/core/google-recaptcha/google-recaptcha.service'; +import { ConfigurationDataService } from 'src/app/core/data/configuration-data.service'; +import { ConfigurationProperty } from 'src/app/core/shared/configuration-property.model'; +import { KlaroService } from 'src/app/shared/cookies/klaro.service'; +import { AlertType } from 'src/app/shared/alert/alert-type'; + @Component({ selector: 'ds-bitstream-request-a-copy-page', @@ -42,18 +49,51 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { bitstream$: Observable; bitstream: Bitstream; bitstreamName: string; + form: UntypedFormGroup; + /** + * request verification configuration + */ + requestCopyVerification = false; + subscriptions: Subscription[] = []; + /** + * The message prefix + */ + @Input() + MESSAGE_PREFIX = 'request-copy-page.registration'; + /** + * Return true if the user completed the reCaptcha verification (checkbox mode) + */ + checkboxCheckedSubject$ = new BehaviorSubject(false); + disableUntilChecked = true; + captchaToken: string; + public AlertTypeEnum = AlertType; + captchaVersion(): Observable { + this.cdRef.detectChanges(); + return this.googleRecaptchaService.captchaVersion(); + + } + + captchaMode(): Observable { + this.cdRef.detectChanges(); + return this.googleRecaptchaService.captchaMode(); + } constructor(private location: Location, - private translateService: TranslateService, - private route: ActivatedRoute, - protected router: Router, - private authorizationService: AuthorizationDataService, - private auth: AuthService, - private formBuilder: UntypedFormBuilder, - private itemRequestDataService: ItemRequestDataService, - private notificationsService: NotificationsService, - private dsoNameService: DSONameService, - private bitstreamService: BitstreamDataService, + private translateService: TranslateService, + private route: ActivatedRoute, + protected router: Router, + private authorizationService: AuthorizationDataService, + private auth: AuthService, + private formBuilder: UntypedFormBuilder, + private itemRequestDataService: ItemRequestDataService, + private notificationsService: NotificationsService, + private dsoNameService: DSONameService, + private bitstreamService: BitstreamDataService, + public cookieService: CookieService, + public googleRecaptchaService: GoogleRecaptchaService, + private cdRef: ChangeDetectorRef, + private configService: ConfigurationDataService, + @Optional() public klaroService: KlaroService ) { } @@ -101,10 +141,20 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { this.subs.push(observableCombineLatest([this.canDownload$, canRequestCopy$]).subscribe(([canDownload, canRequestCopy]) => { if (!canDownload && !canRequestCopy) { - this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); + this.router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: true }); } })); this.initValues(); + this.subscriptions.push(this.configService.findByPropertyName('requestcopy.verification.enabled').pipe( + getFirstSucceededRemoteDataPayload(), + map((res: ConfigurationProperty) => res?.values[0].toLowerCase() === 'true') + ).subscribe((res: boolean) => { + this.requestCopyVerification = res; + })); + this.subscriptions.push(this.disableUntilCheckedFcn().subscribe((res) => { + this.disableUntilChecked = res; + this.cdRef.detectChanges(); + })); } get name() { @@ -128,13 +178,13 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { */ private initValues() { this.getCurrentUser().pipe(take(1)).subscribe((user) => { - this.requestCopyForm.patchValue({allfiles: 'true'}); + this.requestCopyForm.patchValue({ allfiles: 'true' }); if (hasValue(user)) { - this.requestCopyForm.patchValue({name: user.name, email: user.email}); + this.requestCopyForm.patchValue({ name: user.name, email: user.email }); } }); this.bitstream$.pipe(take(1)).subscribe((bitstream) => { - this.requestCopyForm.patchValue({allfiles: 'false'}); + this.requestCopyForm.patchValue({ allfiles: 'false' }); }); } @@ -169,8 +219,8 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { itemRequest.requestEmail = this.email.value; itemRequest.requestName = this.name.value; itemRequest.requestMessage = this.message.value; - - this.itemRequestDataService.requestACopy(itemRequest).pipe( + console.log(this.captchaToken); + this.itemRequestDataService.requestACopy(itemRequest, this.captchaToken).pipe( getFirstCompletedRemoteData() ).subscribe((rd) => { if (rd.hasSucceeded) { @@ -196,6 +246,7 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { * Navigates back to the user's previous location */ navigateBack() { + this.resetForm(); this.location.back(); } @@ -209,4 +260,103 @@ export class BitstreamRequestACopyPageComponent implements OnInit, OnDestroy { getBitstreamLink() { return [getBitstreamDownloadRoute(this.bitstream)]; } + + /** + * execute the captcha function for v2 invisible + */ + executeRecaptcha() { + this.googleRecaptchaService.executeRecaptcha(); + } + + /** + * Request Copy + */ + requestCopy(tokenV2?) { + if (!this.requestCopyForm.invalid) { + if (!this.requestCopyVerification) { + this.subscriptions.push(combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( + switchMap(([captchaVersion, captchaMode]) => { + if (captchaVersion === 'v3') { + return this.googleRecaptchaService.getRecaptchaToken('register_email'); + } else if (captchaVersion === 'v2' && captchaMode === 'checkbox') { + return of(this.googleRecaptchaService.getRecaptchaTokenResponse()); + } else if (captchaVersion === 'v2' && captchaMode === 'invisible') { + return of(tokenV2); + } else { + console.error(`Invalid reCaptcha configuration: version = ${captchaVersion}, mode = ${captchaMode}`); + this.showNotification('error'); + } + }), + take(1), + ).subscribe((token) => { + if (isNotEmpty(token)) { + this.captchaToken = token; + this.requestCopyVerification = true; + + } else { + this.showNotification('error'); + } + } + )); + } else { + + this.requestCopyVerification = true; + } + } + } + + /** + * Return true if the user has accepted the required cookies for reCaptcha + */ + isRecaptchaCookieAccepted(): boolean { + const klaroAnonymousCookie = this.cookieService.get('klaro-anonymous'); + return isNotEmpty(klaroAnonymousCookie) ? klaroAnonymousCookie[CAPTCHA_NAME] : false; + } + + /** + * Return true if the user has not completed the reCaptcha verification (checkbox mode) + */ + disableUntilCheckedFcn(): Observable { + const checked$ = this.checkboxCheckedSubject$.asObservable(); + return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe( + // disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode + switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)), + startWith(true), + ); + } + + onCheckboxChecked(checked: boolean) { + this.checkboxCheckedSubject$.next(checked); + if (!!checked) { + console.log(this.requestCopyForm.invalid); + if (!this.requestCopyForm.invalid) { + this.requestCopyVerification = true; + this.cdRef.detectChanges(); + } + } + } + + /** + * Show a notification to the user + * @param key + */ + showNotification(key) { + const notificationTitle = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.title'); + const notificationErrorMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.error'); + const notificationExpiredMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.expired'); + switch (key) { + case 'expired': + this.notificationsService.warning(notificationTitle, notificationExpiredMsg); + break; + case 'error': + this.notificationsService.error(notificationTitle, notificationErrorMsg); + break; + default: + console.warn(`Unimplemented notification '${key}' from reCaptcha service`); + } + } + + resetForm() { + this.requestCopyForm.reset(); + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 541bb4f39d3..67426147c70 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6310,6 +6310,10 @@ "ldn.no-filter.label": "None", + "request-copy-page.registration.google-recaptcha.must-accept-cookies": "In order to request copy you must accept the Request a copy of the file (Google reCaptcha) cookies.", + + "request-copy-page.registration.google-recaptcha.open-cookie-settings": "Open cookie settings", + "admin.notify.dashboard": "Dashboard", "menu.section.notify_dashboard": "Dashboard", @@ -6641,4 +6645,5 @@ "search.filters.filter.notifyEndorsement.placeholder": "Notify Endorsement", "search.filters.filter.notifyEndorsement.label": "Search Notify Endorsement", + }