From 14ba4869a3621405b7ae13c713059b21748a155e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 11 Feb 2021 08:55:49 +0100 Subject: [PATCH] MOBILE-3651 quiz: Implement access rules --- .../quiz/accessrules/accessrules.module.ts | 43 ++++ .../delaybetweenattempts.module.ts | 34 +++ .../services/handlers/delaybetweenattempts.ts | 54 +++++ .../accessrules/ipaddress/ipaddress.module.ts | 34 +++ .../ipaddress/services/handlers/ipaddress.ts | 53 +++++ .../numattempts/numattempts.module.ts | 34 +++ .../services/handlers/numattempts.ts | 53 +++++ ...ddon-mod-quiz-access-offline-attempts.html | 6 + .../component/offlineattempts.ts | 56 +++++ .../offlineattempts/offlineattempts.module.ts | 43 ++++ .../services/handlers/offlineattempts.ts | 102 +++++++++ .../openclosedate/openclosedate.module.ts | 34 +++ .../services/handlers/openclosedate.ts | 76 +++++++ .../addon-mod-quiz-access-password.html | 14 ++ .../password/component/password.ts | 46 ++++ .../accessrules/password/password.module.ts | 51 +++++ .../password/services/database/password.ts | 53 +++++ .../password/services/handlers/password.ts | 198 ++++++++++++++++++ .../safebrowser/safebrowser.module.ts | 34 +++ .../services/handlers/safebrowser.ts | 53 +++++ .../securewindow/securewindow.module.ts | 34 +++ .../services/handlers/securewindow.ts | 53 +++++ .../addon-mod-quiz-access-time-limit.html | 6 + .../timelimit/component/timelimit.ts | 47 +++++ .../timelimit/services/handlers/timelimit.ts | 83 ++++++++ .../accessrules/timelimit/timelimit.module.ts | 43 ++++ src/addons/mod/quiz/quiz.module.ts | 2 + 27 files changed, 1339 insertions(+) create mode 100644 src/addons/mod/quiz/accessrules/accessrules.module.ts create mode 100644 src/addons/mod/quiz/accessrules/delaybetweenattempts/delaybetweenattempts.module.ts create mode 100644 src/addons/mod/quiz/accessrules/delaybetweenattempts/services/handlers/delaybetweenattempts.ts create mode 100644 src/addons/mod/quiz/accessrules/ipaddress/ipaddress.module.ts create mode 100644 src/addons/mod/quiz/accessrules/ipaddress/services/handlers/ipaddress.ts create mode 100644 src/addons/mod/quiz/accessrules/numattempts/numattempts.module.ts create mode 100644 src/addons/mod/quiz/accessrules/numattempts/services/handlers/numattempts.ts create mode 100644 src/addons/mod/quiz/accessrules/offlineattempts/component/addon-mod-quiz-access-offline-attempts.html create mode 100644 src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts create mode 100644 src/addons/mod/quiz/accessrules/offlineattempts/offlineattempts.module.ts create mode 100644 src/addons/mod/quiz/accessrules/offlineattempts/services/handlers/offlineattempts.ts create mode 100644 src/addons/mod/quiz/accessrules/openclosedate/openclosedate.module.ts create mode 100644 src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts create mode 100644 src/addons/mod/quiz/accessrules/password/component/addon-mod-quiz-access-password.html create mode 100644 src/addons/mod/quiz/accessrules/password/component/password.ts create mode 100644 src/addons/mod/quiz/accessrules/password/password.module.ts create mode 100644 src/addons/mod/quiz/accessrules/password/services/database/password.ts create mode 100644 src/addons/mod/quiz/accessrules/password/services/handlers/password.ts create mode 100644 src/addons/mod/quiz/accessrules/safebrowser/safebrowser.module.ts create mode 100644 src/addons/mod/quiz/accessrules/safebrowser/services/handlers/safebrowser.ts create mode 100644 src/addons/mod/quiz/accessrules/securewindow/securewindow.module.ts create mode 100644 src/addons/mod/quiz/accessrules/securewindow/services/handlers/securewindow.ts create mode 100644 src/addons/mod/quiz/accessrules/timelimit/component/addon-mod-quiz-access-time-limit.html create mode 100644 src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts create mode 100644 src/addons/mod/quiz/accessrules/timelimit/services/handlers/timelimit.ts create mode 100644 src/addons/mod/quiz/accessrules/timelimit/timelimit.module.ts diff --git a/src/addons/mod/quiz/accessrules/accessrules.module.ts b/src/addons/mod/quiz/accessrules/accessrules.module.ts new file mode 100644 index 000000000..efd3206b9 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/accessrules.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; + +import { AddonModQuizAccessDelayBetweenAttemptsModule } from './delaybetweenattempts/delaybetweenattempts.module'; +import { AddonModQuizAccessIpAddressModule } from './ipaddress/ipaddress.module'; +import { AddonModQuizAccessNumAttemptsModule } from './numattempts/numattempts.module'; +import { AddonModQuizAccessOfflineAttemptsModule } from './offlineattempts/offlineattempts.module'; +import { AddonModQuizAccessOpenCloseDateModule } from './openclosedate/openclosedate.module'; +import { AddonModQuizAccessPasswordModule } from './password/password.module'; +import { AddonModQuizAccessSafeBrowserModule } from './safebrowser/safebrowser.module'; +import { AddonModQuizAccessSecureWindowModule } from './securewindow/securewindow.module'; +import { AddonModQuizAccessTimeLimitModule } from './timelimit/timelimit.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonModQuizAccessDelayBetweenAttemptsModule, + AddonModQuizAccessIpAddressModule, + AddonModQuizAccessNumAttemptsModule, + AddonModQuizAccessOfflineAttemptsModule, + AddonModQuizAccessOpenCloseDateModule, + AddonModQuizAccessPasswordModule, + AddonModQuizAccessSafeBrowserModule, + AddonModQuizAccessSecureWindowModule, + AddonModQuizAccessTimeLimitModule, + ], + providers: [], + exports: [], +}) +export class AddonModQuizAccessRulesModule { } diff --git a/src/addons/mod/quiz/accessrules/delaybetweenattempts/delaybetweenattempts.module.ts b/src/addons/mod/quiz/accessrules/delaybetweenattempts/delaybetweenattempts.module.ts new file mode 100644 index 000000000..feefd7d1f --- /dev/null +++ b/src/addons/mod/quiz/accessrules/delaybetweenattempts/delaybetweenattempts.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessDelayBetweenAttemptsHandler } from './services/handlers/delaybetweenattempts'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessDelayBetweenAttemptsHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessDelayBetweenAttemptsModule {} diff --git a/src/addons/mod/quiz/accessrules/delaybetweenattempts/services/handlers/delaybetweenattempts.ts b/src/addons/mod/quiz/accessrules/delaybetweenattempts/services/handlers/delaybetweenattempts.ts new file mode 100644 index 000000000..e66527e48 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/delaybetweenattempts/services/handlers/delaybetweenattempts.ts @@ -0,0 +1,54 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support delay between attempts access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessDelayBetweenAttemptsHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessDelayBetweenAttempts'; + ruleName = 'quizaccess_delaybetweenattempts'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + +} + +export class AddonModQuizAccessDelayBetweenAttemptsHandler + extends makeSingleton(AddonModQuizAccessDelayBetweenAttemptsHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/ipaddress/ipaddress.module.ts b/src/addons/mod/quiz/accessrules/ipaddress/ipaddress.module.ts new file mode 100644 index 000000000..693912758 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/ipaddress/ipaddress.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessIpAddressHandler } from './services/handlers/ipaddress'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessIpAddressHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessIpAddressModule {} diff --git a/src/addons/mod/quiz/accessrules/ipaddress/services/handlers/ipaddress.ts b/src/addons/mod/quiz/accessrules/ipaddress/services/handlers/ipaddress.ts new file mode 100644 index 000000000..ca96e8ef7 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/ipaddress/services/handlers/ipaddress.ts @@ -0,0 +1,53 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support IP address access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessIpAddressHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessIpAddress'; + ruleName = 'quizaccess_ipaddress'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + +} + +export class AddonModQuizAccessIpAddressHandler extends makeSingleton(AddonModQuizAccessIpAddressHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/numattempts/numattempts.module.ts b/src/addons/mod/quiz/accessrules/numattempts/numattempts.module.ts new file mode 100644 index 000000000..5ce73b554 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/numattempts/numattempts.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessNumAttemptsHandler } from './services/handlers/numattempts'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessNumAttemptsHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessNumAttemptsModule {} diff --git a/src/addons/mod/quiz/accessrules/numattempts/services/handlers/numattempts.ts b/src/addons/mod/quiz/accessrules/numattempts/services/handlers/numattempts.ts new file mode 100644 index 000000000..347bc6a77 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/numattempts/services/handlers/numattempts.ts @@ -0,0 +1,53 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support num attempts access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessNumAttemptsHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessNumAttempts'; + ruleName = 'quizaccess_numattempts'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + +} + +export class AddonModQuizAccessNumAttemptsHandler extends makeSingleton(AddonModQuizAccessNumAttemptsHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/offlineattempts/component/addon-mod-quiz-access-offline-attempts.html b/src/addons/mod/quiz/accessrules/offlineattempts/component/addon-mod-quiz-access-offline-attempts.html new file mode 100644 index 000000000..e99c2d3b2 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/offlineattempts/component/addon-mod-quiz-access-offline-attempts.html @@ -0,0 +1,6 @@ + + +

{{ 'core.settings.synchronization' | translate }}

+

{{ 'addon.mod_quiz.confirmcontinueoffline' | translate:{$a: syncTimeReadable} }}

+
+
diff --git a/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts b/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts new file mode 100644 index 000000000..d6566ebc0 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/offlineattempts/component/offlineattempts.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; +import { AddonModQuizSync } from '@addons/mod/quiz/services/quiz-sync'; +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; + +/** + * Component to render the preflight for offline attempts. + */ +@Component({ + selector: 'addon-mod-quiz-access-offline-attempts', + templateUrl: 'addon-mod-quiz-access-offline-attempts.html', +}) +export class AddonModQuizAccessOfflineAttemptsComponent implements OnInit { + + @Input() rule?: string; // The name of the rule. + @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to. + @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued. + @Input() prefetch?: boolean; // Whether the user is prefetching the quiz. + @Input() siteId?: string; // Site ID. + @Input() form?: FormGroup; // Form where to add the form control. + + syncTimeReadable = ''; + + constructor(private fb: FormBuilder) { } + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + // Always set confirmdatasaved to 1. Sending the data means the user accepted. + this.form?.addControl('confirmdatasaved', this.fb.control(1)); + + if (!this.quiz) { + return; + } + + const time = await AddonModQuizSync.instance.getSyncTime(this.quiz.id); + + this.syncTimeReadable = AddonModQuizSync.instance.getReadableTimeFromTimestamp(time); + } + +} diff --git a/src/addons/mod/quiz/accessrules/offlineattempts/offlineattempts.module.ts b/src/addons/mod/quiz/accessrules/offlineattempts/offlineattempts.module.ts new file mode 100644 index 000000000..bfd496662 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/offlineattempts/offlineattempts.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonModQuizAccessOfflineAttemptsComponent } from './component/offlineattempts'; +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessOfflineAttemptsHandler } from './services/handlers/offlineattempts'; + +@NgModule({ + declarations: [ + AddonModQuizAccessOfflineAttemptsComponent, + ], + imports: [ + CoreSharedModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessOfflineAttemptsHandler.instance); + }, + }, + ], + exports: [ + AddonModQuizAccessOfflineAttemptsComponent, + ], +}) +export class AddonModQuizAccessOfflineAttemptsModule {} diff --git a/src/addons/mod/quiz/accessrules/offlineattempts/services/handlers/offlineattempts.ts b/src/addons/mod/quiz/accessrules/offlineattempts/services/handlers/offlineattempts.ts new file mode 100644 index 000000000..d0d7ba2af --- /dev/null +++ b/src/addons/mod/quiz/accessrules/offlineattempts/services/handlers/offlineattempts.ts @@ -0,0 +1,102 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Type } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; +import { AddonModQuizAccessOfflineAttemptsComponent } from '../../component/offlineattempts'; +import { AddonModQuizSync } from '@addons/mod/quiz/services/quiz-sync'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support offline attempts access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessOfflineAttemptsHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessOfflineAttempts'; + ruleName = 'quizaccess_offlineattempts'; + + /** + * Add preflight data that doesn't require user interaction. The data should be added to the preflightData param. + * + * @param quiz The quiz the rule belongs to. + * @param preflightData Object where to add the preflight data. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. + */ + getFixedPreflightData( + quiz: AddonModQuizQuizWSData, + preflightData: Record, + ): void | Promise { + preflightData.confirmdatasaved = '1'; + } + + /** + * Return the Component to use to display the access rule preflight. + * Implement this if your access rule requires a preflight check with user interaction. + * It's recommended to return the class of the component, but you can also return an instance of the component. + * + * @return The component (or promise resolved with component) to use, undefined if not found. + */ + getPreflightComponent(): Type | Promise> { + return AddonModQuizAccessOfflineAttemptsComponent; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + async isPreflightCheckRequired( + quiz: AddonModQuizQuizWSData, + attempt?: AddonModQuizAttemptWSData, + prefetch?: boolean, + siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars + ): Promise { + if (prefetch) { + // Don't show the warning if the user is prefetching. + return false; + } + + if (!attempt) { + // User is starting a new attempt, show the warning. + return true; + } + + const syncTime = await AddonModQuizSync.instance.getSyncTime(quiz.id); + + // Show warning if last sync was a while ago. + return Date.now() - AddonModQuizSync.instance.syncInterval > syncTime; + } + +} + +export class AddonModQuizAccessOfflineAttemptsHandler extends makeSingleton(AddonModQuizAccessOfflineAttemptsHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/openclosedate/openclosedate.module.ts b/src/addons/mod/quiz/accessrules/openclosedate/openclosedate.module.ts new file mode 100644 index 000000000..3f19048f8 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/openclosedate/openclosedate.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessOpenCloseDateHandler } from './services/handlers/openclosedate'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessOpenCloseDateHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessOpenCloseDateModule {} diff --git a/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts b/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts new file mode 100644 index 000000000..58d2854ef --- /dev/null +++ b/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts @@ -0,0 +1,76 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { AddonModQuizAttemptWSData, AddonModQuizProvider } from '@addons/mod/quiz/services/quiz'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support open/close date access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessOpenCloseDateHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessOpenCloseDate'; + ruleName = 'quizaccess_openclosedate'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + + /** + * Whether or not the time left of an attempt should be displayed. + * + * @param attempt The attempt. + * @param endTime The attempt end time (in seconds). + * @param timeNow The current time in seconds. + * @return Whether it should be displayed. + */ + shouldShowTimeLeft(attempt: AddonModQuizAttemptWSData, endTime: number, timeNow: number): boolean { + // If this is a teacher preview after the close date, do not show the time. + if (attempt.preview && timeNow > endTime) { + return false; + } + + // Show the time left only if it's less than QUIZ_SHOW_TIME_BEFORE_DEADLINE. + if (timeNow > endTime - AddonModQuizProvider.QUIZ_SHOW_TIME_BEFORE_DEADLINE) { + return true; + } + + return false; + } + +} + +export class AddonModQuizAccessOpenCloseDateHandler extends makeSingleton(AddonModQuizAccessOpenCloseDateHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/password/component/addon-mod-quiz-access-password.html b/src/addons/mod/quiz/accessrules/password/component/addon-mod-quiz-access-password.html new file mode 100644 index 000000000..5ab743e6f --- /dev/null +++ b/src/addons/mod/quiz/accessrules/password/component/addon-mod-quiz-access-password.html @@ -0,0 +1,14 @@ + + +

{{ 'addon.mod_quiz.quizpassword' | translate }}

+

{{ 'addon.mod_quiz.requirepasswordmessage' | translate}}

+
+
+ + + + + + + diff --git a/src/addons/mod/quiz/accessrules/password/component/password.ts b/src/addons/mod/quiz/accessrules/password/component/password.ts new file mode 100644 index 000000000..95100a976 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/password/component/password.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit, Input } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; + +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; + +/** + * Component to render the preflight for password. + */ +@Component({ + selector: 'addon-mod-quiz-access-password', + templateUrl: 'addon-mod-quiz-access-password.html', +}) +export class AddonModQuizAccessPasswordComponent implements OnInit { + + @Input() rule?: string; // The name of the rule. + @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to. + @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued. + @Input() prefetch?: boolean; // Whether the user is prefetching the quiz. + @Input() siteId?: string; // Site ID. + @Input() form?: FormGroup; // Form where to add the form control. + + constructor(private fb: FormBuilder) { } + + /** + * Component being initialized. + */ + ngOnInit(): void { + // Add the control for the password. + this.form?.addControl('quizpassword', this.fb.control('')); + } + +} diff --git a/src/addons/mod/quiz/accessrules/password/password.module.ts b/src/addons/mod/quiz/accessrules/password/password.module.ts new file mode 100644 index 000000000..ebad48b4c --- /dev/null +++ b/src/addons/mod/quiz/accessrules/password/password.module.ts @@ -0,0 +1,51 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +import { CoreSharedModule } from '@/core/shared.module'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { AddonModQuizAccessPasswordComponent } from './component/password'; +import { AddonModQuizAccessPasswordHandler } from './services/handlers/password'; +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { SITE_SCHEMA } from './services/database/password'; + +@NgModule({ + declarations: [ + AddonModQuizAccessPasswordComponent, + ], + imports: [ + CoreSharedModule, + ], + providers: [ + { + provide: CORE_SITE_SCHEMAS, + useValue: [SITE_SCHEMA], + multi: true, + }, + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessPasswordHandler.instance); + }, + }, + ], + exports: [ + AddonModQuizAccessPasswordComponent, + ], +}) +export class AddonModQuizAccessPasswordModule {} diff --git a/src/addons/mod/quiz/accessrules/password/services/database/password.ts b/src/addons/mod/quiz/accessrules/password/services/database/password.ts new file mode 100644 index 000000000..cf67bd039 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/password/services/database/password.ts @@ -0,0 +1,53 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSiteSchema } from '@services/sites'; + +/** + * Database variables for AddonModQuizAccessPasswordHandlerService. + */ +export const PASSWORD_TABLE_NAME = 'addon_mod_quiz_access_password'; +export const SITE_SCHEMA: CoreSiteSchema = { + name: 'AddonModQuizAccessPasswordHandler', + version: 1, + tables: [ + { + name: PASSWORD_TABLE_NAME, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true, + }, + { + name: 'password', + type: 'TEXT', + }, + { + name: 'timemodified', + type: 'INTEGER', + }, + ], + }, + ], +}; + +/** + * Quiz attempt. + */ +export type AddonModQuizAccessPasswordDBRecord = { + id: number; + password: string; + timemodified: number; +}; diff --git a/src/addons/mod/quiz/accessrules/password/services/handlers/password.ts b/src/addons/mod/quiz/accessrules/password/services/handlers/password.ts new file mode 100644 index 000000000..3e37f4331 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/password/services/handlers/password.ts @@ -0,0 +1,198 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Type } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; +import { CoreSites } from '@services/sites'; +import { AddonModQuizAccessPasswordDBRecord, PASSWORD_TABLE_NAME } from '../database/password'; +import { AddonModQuizAccessPasswordComponent } from '../../component/password'; +import { CoreUtils } from '@services/utils/utils'; + +/** + * Handler to support password access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessPasswordHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessPassword'; + ruleName = 'quizaccess_password'; + + /** + * Add preflight data that doesn't require user interaction. The data should be added to the preflightData param. + * + * @param quiz The quiz the rule belongs to. + * @param preflightData Object where to add the preflight data. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. + */ + async getFixedPreflightData( + quiz: AddonModQuizQuizWSData, + preflightData: Record, + attempt?: AddonModQuizAttemptWSData, + prefetch?: boolean, + siteId?: string, + ): Promise { + if (typeof preflightData.quizpassword != 'undefined') { + return; + } + + try { + // Try to get a password stored. If it's found, use it. + const entry = await this.getPasswordEntry(quiz.id, siteId); + + preflightData.quizpassword = entry.password; + } catch { + // No password stored. + } + } + + /** + * Get a password stored in DB. + * + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the DB entry on success. + */ + protected async getPasswordEntry(quizId: number, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return site.getDb().getRecord(PASSWORD_TABLE_NAME, { id: quizId }); + } + + /** + * Return the Component to use to display the access rule preflight. + * Implement this if your access rule requires a preflight check with user interaction. + * It's recommended to return the class of the component, but you can also return an instance of the component. + * + * @return The component (or promise resolved with component) to use, undefined if not found. + */ + getPreflightComponent(): Type | Promise> { + return AddonModQuizAccessPasswordComponent; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + async isPreflightCheckRequired( + quiz: AddonModQuizQuizWSData, + attempt?: AddonModQuizAttemptWSData, + prefetch?: boolean, + siteId?: string, + ): Promise { + // If there's a password stored don't require the preflight since we'll use the stored one. + const entry = await CoreUtils.instance.ignoreErrors(this.getPasswordEntry(quiz.id, siteId)); + + return !entry; + } + + /** + * Function called when the preflight check has passed. This is a chance to record that fact in some way. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. + */ + async notifyPreflightCheckPassed( + quiz: AddonModQuizQuizWSData, + attempt: AddonModQuizAttemptWSData | undefined, + preflightData: Record, + prefetch?: boolean, + siteId?: string, + ): Promise { + // The password is right, store it to use it automatically in following executions. + if (typeof preflightData.quizpassword != 'undefined') { + return this.storePassword(quiz.id, preflightData.quizpassword, siteId); + } + } + + /** + * Function called when the preflight check fails. This is a chance to record that fact in some way. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. + */ + notifyPreflightCheckFailed?( + quiz: AddonModQuizQuizWSData, + attempt: AddonModQuizAttemptWSData | undefined, + preflightData: Record, + prefetch?: boolean, + siteId?: string, + ): Promise { + // The password is wrong, remove it from DB if it's there. + return this.removePassword(quiz.id, siteId); + } + + /** + * Remove a password from DB. + * + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected async removePassword(quizId: number, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.getDb().deleteRecords(PASSWORD_TABLE_NAME, { id: quizId }); + } + + /** + * Store a password in DB. + * + * @param quizId Quiz ID. + * @param password Password. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected async storePassword(quizId: number, password: string, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + const entry: AddonModQuizAccessPasswordDBRecord = { + id: quizId, + password, + timemodified: Date.now(), + }; + + await site.getDb().insertRecord(PASSWORD_TABLE_NAME, entry); + } + +} + +export class AddonModQuizAccessPasswordHandler extends makeSingleton(AddonModQuizAccessPasswordHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/safebrowser/safebrowser.module.ts b/src/addons/mod/quiz/accessrules/safebrowser/safebrowser.module.ts new file mode 100644 index 000000000..8078e2f93 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/safebrowser/safebrowser.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessSafeBrowserHandler } from './services/handlers/safebrowser'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessSafeBrowserHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessSafeBrowserModule {} diff --git a/src/addons/mod/quiz/accessrules/safebrowser/services/handlers/safebrowser.ts b/src/addons/mod/quiz/accessrules/safebrowser/services/handlers/safebrowser.ts new file mode 100644 index 000000000..f85bba085 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/safebrowser/services/handlers/safebrowser.ts @@ -0,0 +1,53 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support safe address access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessSafeBrowserHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessSafeBrowser'; + ruleName = 'quizaccess_safebrowser'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + +} + +export class AddonModQuizAccessSafeBrowserHandler extends makeSingleton(AddonModQuizAccessSafeBrowserHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/securewindow/securewindow.module.ts b/src/addons/mod/quiz/accessrules/securewindow/securewindow.module.ts new file mode 100644 index 000000000..748371fb9 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/securewindow/securewindow.module.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessSecureWindowHandler } from './services/handlers/securewindow'; + +@NgModule({ + declarations: [ + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessSecureWindowHandler.instance); + }, + }, + ], +}) +export class AddonModQuizAccessSecureWindowModule {} diff --git a/src/addons/mod/quiz/accessrules/securewindow/services/handlers/securewindow.ts b/src/addons/mod/quiz/accessrules/securewindow/services/handlers/securewindow.ts new file mode 100644 index 000000000..e44bc366a --- /dev/null +++ b/src/addons/mod/quiz/accessrules/securewindow/services/handlers/securewindow.ts @@ -0,0 +1,53 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support secure window access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessSecureWindowHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessSecureWindow'; + ruleName = 'quizaccess_securewindow'; + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired(): boolean | Promise { + return false; + } + +} + +export class AddonModQuizAccessSecureWindowHandler extends makeSingleton(AddonModQuizAccessSecureWindowHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/timelimit/component/addon-mod-quiz-access-time-limit.html b/src/addons/mod/quiz/accessrules/timelimit/component/addon-mod-quiz-access-time-limit.html new file mode 100644 index 000000000..b48075dbf --- /dev/null +++ b/src/addons/mod/quiz/accessrules/timelimit/component/addon-mod-quiz-access-time-limit.html @@ -0,0 +1,6 @@ + + +

{{ 'addon.mod_quiz.confirmstartheader' | translate }}

+

{{ 'addon.mod_quiz.confirmstart' | translate:{$a: readableTimeLimit} }}

+
+
diff --git a/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts b/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts new file mode 100644 index 000000000..0cf94ba9b --- /dev/null +++ b/src/addons/mod/quiz/accessrules/timelimit/component/timelimit.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; +import { CoreTimeUtils } from '@services/utils/time'; + +/** + * Component to render the preflight for time limit. + */ +@Component({ + selector: 'addon-mod-quiz-access-time-limit', + templateUrl: 'addon-mod-quiz-access-time-limit.html', +}) +export class AddonModQuizAccessTimeLimitComponent implements OnInit { + + @Input() rule?: string; // The name of the rule. + @Input() quiz?: AddonModQuizQuizWSData; // The quiz the rule belongs to. + @Input() attempt?: AddonModQuizAttemptWSData; // The attempt being started/continued. + @Input() prefetch?: boolean; // Whether the user is prefetching the quiz. + @Input() siteId?: string; // Site ID. + @Input() form?: FormGroup; // Form where to add the form control. + + readableTimeLimit = ''; + + ngOnInit(): void { + if (!this.quiz?.timelimit) { + return; + } + + this.readableTimeLimit = CoreTimeUtils.instance.formatTime(this.quiz?.timelimit); + } + +} diff --git a/src/addons/mod/quiz/accessrules/timelimit/services/handlers/timelimit.ts b/src/addons/mod/quiz/accessrules/timelimit/services/handlers/timelimit.ts new file mode 100644 index 000000000..ee8c7bf1a --- /dev/null +++ b/src/addons/mod/quiz/accessrules/timelimit/services/handlers/timelimit.ts @@ -0,0 +1,83 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Type } from '@angular/core'; + +import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from '@addons/mod/quiz/services/quiz'; +import { AddonModQuizAccessTimeLimitComponent } from '../../component/timelimit'; +import { makeSingleton } from '@singletons'; + +/** + * Handler to support time limit access rule. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModQuizAccessTimeLimitHandlerService implements AddonModQuizAccessRuleHandler { + + name = 'AddonModQuizAccessTimeLimit'; + ruleName = 'quizaccess_timelimit'; + + /** + * Return the Component to use to display the access rule preflight. + * Implement this if your access rule requires a preflight check with user interaction. + * It's recommended to return the class of the component, but you can also return an instance of the component. + * + * @return The component (or promise resolved with component) to use, undefined if not found. + */ + getPreflightComponent(): Type | Promise> { + return AddonModQuizAccessTimeLimitComponent; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return True or promise resolved with true if enabled. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Whether the rule requires a preflight check when prefetch/start/continue an attempt. + * + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. + */ + isPreflightCheckRequired( + quiz: AddonModQuizQuizWSData, + attempt?: AddonModQuizAttemptWSData, + ): boolean | Promise { + // Warning only required if the attempt is not already started. + return !attempt; + } + + /** + * Whether or not the time left of an attempt should be displayed. + * + * @param attempt The attempt. + * @param endTime The attempt end time (in seconds). + * @param timeNow The current time in seconds. + * @return Whether it should be displayed. + */ + shouldShowTimeLeft(attempt: AddonModQuizAttemptWSData, endTime: number, timeNow: number): boolean { + // If this is a teacher preview after the time limit expires, don't show the time left. + return !(attempt.preview && timeNow > endTime); + } + +} + +export class AddonModQuizAccessTimeLimitHandler extends makeSingleton(AddonModQuizAccessTimeLimitHandlerService) {} diff --git a/src/addons/mod/quiz/accessrules/timelimit/timelimit.module.ts b/src/addons/mod/quiz/accessrules/timelimit/timelimit.module.ts new file mode 100644 index 000000000..f482484f4 --- /dev/null +++ b/src/addons/mod/quiz/accessrules/timelimit/timelimit.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonModQuizAccessTimeLimitComponent } from './component/timelimit'; +import { AddonModQuizAccessRuleDelegate } from '../../services/access-rules-delegate'; +import { AddonModQuizAccessTimeLimitHandler } from './services/handlers/timelimit'; + +@NgModule({ + declarations: [ + AddonModQuizAccessTimeLimitComponent, + ], + imports: [ + CoreSharedModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonModQuizAccessRuleDelegate.instance.registerHandler(AddonModQuizAccessTimeLimitHandler.instance); + }, + }, + ], + exports: [ + AddonModQuizAccessTimeLimitComponent, + ], +}) +export class AddonModQuizAccessTimeLimitModule {} diff --git a/src/addons/mod/quiz/quiz.module.ts b/src/addons/mod/quiz/quiz.module.ts index 601d4f3c3..3dcf54d19 100644 --- a/src/addons/mod/quiz/quiz.module.ts +++ b/src/addons/mod/quiz/quiz.module.ts @@ -19,6 +19,7 @@ import { CoreCourseModuleDelegate } from '@features/course/services/module-deleg import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { AddonModQuizAccessRulesModule } from './accessrules/accessrules.module'; import { AddonModQuizComponentsModule } from './components/components.module'; import { SITE_SCHEMA } from './services/database/quiz'; import { AddonModQuizModuleHandler, AddonModQuizModuleHandlerService } from './services/handlers/module'; @@ -35,6 +36,7 @@ const routes: Routes = [ imports: [ CoreMainMenuTabRoutingModule.forChild(routes), AddonModQuizComponentsModule, + AddonModQuizAccessRulesModule, ], providers: [ {