commit
271e012493
|
@ -3,7 +3,7 @@
|
||||||
# Functions used to create langidex.
|
# Functions used to create langidex.
|
||||||
#
|
#
|
||||||
|
|
||||||
SERVER_URL='https://download.moodle.org/'
|
SERVER_URL='https://packaging.moodle.org/'
|
||||||
|
|
||||||
# Downloads a file and if it's a zip file, unzip it.
|
# Downloads a file and if it's a zip file, unzip it.
|
||||||
function download_file {
|
function download_file {
|
||||||
|
@ -12,7 +12,7 @@ function download_file {
|
||||||
|
|
||||||
pushd "$LANGPACKS_PATH" > /dev/null
|
pushd "$LANGPACKS_PATH" > /dev/null
|
||||||
|
|
||||||
curl -s "$url" --output "$filename" > /dev/null
|
curl -L -s "$url" --output "$filename" > /dev/null
|
||||||
size=$(du -k "$filename" | cut -f 1)
|
size=$(du -k "$filename" | cut -f 1)
|
||||||
if [ ! -n "$filename" ] || [ "$size" -le 1 ]; then
|
if [ ! -n "$filename" ] || [ "$size" -le 1 ]; then
|
||||||
echo "Wrong or corrupt file $filename"
|
echo "Wrong or corrupt file $filename"
|
||||||
|
@ -45,7 +45,7 @@ function get_english {
|
||||||
get_app_version
|
get_app_version
|
||||||
|
|
||||||
echo "Getting English language..."
|
echo "Getting English language..."
|
||||||
download_file "$SERVER_URL/download.php/direct/langpack/$LANGVERSION/en.zip"
|
download_file "$SERVER_URL/langpack/$LANGVERSION/en.zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
#Saves or updates a key on langindex_old.json
|
#Saves or updates a key on langindex_old.json
|
||||||
|
|
|
@ -1582,6 +1582,9 @@
|
||||||
"core.course.errordownloadingsection": "local_moodlemobileapp",
|
"core.course.errordownloadingsection": "local_moodlemobileapp",
|
||||||
"core.course.errorgetmodule": "local_moodlemobileapp",
|
"core.course.errorgetmodule": "local_moodlemobileapp",
|
||||||
"core.course.failed": "completion",
|
"core.course.failed": "completion",
|
||||||
|
"core.course.guestaccess_passwordinvalid": "enrol_guest/passwordinvalid",
|
||||||
|
"core.course.guestaccess_withpassword": "enrol_guest",
|
||||||
|
"core.course.guestaccess": "enrol_guest/pluginname",
|
||||||
"core.course.hiddenfromstudents": "moodle",
|
"core.course.hiddenfromstudents": "moodle",
|
||||||
"core.course.hiddenoncoursepage": "moodle",
|
"core.course.hiddenoncoursepage": "moodle",
|
||||||
"core.course.highlighted": "moodle",
|
"core.course.highlighted": "moodle",
|
||||||
|
|
|
@ -18,13 +18,11 @@ import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
import { AddonModLessonIndexComponent } from './index/index';
|
import { AddonModLessonIndexComponent } from './index/index';
|
||||||
import { AddonModLessonMenuModalPage } from './menu-modal/menu-modal';
|
import { AddonModLessonMenuModalPage } from './menu-modal/menu-modal';
|
||||||
import { AddonModLessonPasswordModalComponent } from './password-modal/password-modal';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AddonModLessonIndexComponent,
|
AddonModLessonIndexComponent,
|
||||||
AddonModLessonMenuModalPage,
|
AddonModLessonMenuModalPage,
|
||||||
AddonModLessonPasswordModalComponent,
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
|
@ -35,7 +33,6 @@ import { AddonModLessonPasswordModalComponent } from './password-modal/password-
|
||||||
exports: [
|
exports: [
|
||||||
AddonModLessonIndexComponent,
|
AddonModLessonIndexComponent,
|
||||||
AddonModLessonMenuModalPage,
|
AddonModLessonMenuModalPage,
|
||||||
AddonModLessonPasswordModalComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonModLessonComponentsModule {}
|
export class AddonModLessonComponentsModule {}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>
|
|
||||||
<h1>{{ 'core.login.password' | translate }}</h1>
|
|
||||||
</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
|
||||||
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
<ion-content class="ion-padding addon-mod_lesson-password-modal">
|
|
||||||
<form (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
|
||||||
<core-show-password name="password">
|
|
||||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" core-auto-focus
|
|
||||||
#passwordinput [clearOnEdit]="false">
|
|
||||||
</ion-input>
|
|
||||||
</core-show-password>
|
|
||||||
</ion-item>
|
|
||||||
<ion-button expand="block" type="submit">
|
|
||||||
{{ 'addon.mod_lesson.continue' | translate }}
|
|
||||||
<ion-icon slot="end" name="fas-chevron-right" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
|
|
||||||
<input type="submit" class="core-submit-hidden-enter" />
|
|
||||||
</form>
|
|
||||||
</ion-content>
|
|
|
@ -1,57 +0,0 @@
|
||||||
// (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, ViewChild, ElementRef } from '@angular/core';
|
|
||||||
import { IonInput } from '@ionic/angular';
|
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreForms } from '@singletons/form';
|
|
||||||
import { ModalController } from '@singletons';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal that asks the password for a lesson.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'page-addon-mod-lesson-password-modal',
|
|
||||||
templateUrl: 'password-modal.html',
|
|
||||||
})
|
|
||||||
export class AddonModLessonPasswordModalComponent {
|
|
||||||
|
|
||||||
@ViewChild('passwordForm') formElement?: ElementRef;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the password back.
|
|
||||||
*
|
|
||||||
* @param e Event.
|
|
||||||
* @param password The input element.
|
|
||||||
*/
|
|
||||||
submitPassword(e: Event, password: IonInput): void {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
|
||||||
|
|
||||||
ModalController.dismiss(password.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close modal.
|
|
||||||
*/
|
|
||||||
closeModal(): void {
|
|
||||||
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
|
||||||
|
|
||||||
ModalController.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -13,7 +13,6 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
||||||
|
@ -26,7 +25,6 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreWSFile } from '@services/ws';
|
import { CoreWSFile } from '@services/ws';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { AddonModLessonPasswordModalComponent } from '../../components/password-modal/password-modal';
|
|
||||||
import {
|
import {
|
||||||
AddonModLesson,
|
AddonModLesson,
|
||||||
AddonModLessonGetAccessInformationWSResponse,
|
AddonModLessonGetAccessInformationWSResponse,
|
||||||
|
@ -48,24 +46,6 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
|
||||||
// Don't check timers to decrease positives. If a user performs some action it will be reflected in other items.
|
// Don't check timers to decrease positives. If a user performs some action it will be reflected in other items.
|
||||||
updatesNames = /^configuration$|^.*files$|^grades$|^gradeitems$|^pages$|^answers$|^questionattempts$|^pagesviewed$/;
|
updatesNames = /^configuration$|^.*files$|^grades$|^gradeitems$|^pages$|^answers$|^questionattempts$|^pagesviewed$/;
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask password.
|
|
||||||
*
|
|
||||||
* @returns Promise resolved with the password.
|
|
||||||
*/
|
|
||||||
protected async askUserPassword(): Promise<string> {
|
|
||||||
// Create and show the modal.
|
|
||||||
const modalData = await CoreDomUtils.openModal<string>({
|
|
||||||
component: AddonModLessonPasswordModalComponent,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof modalData != 'string') {
|
|
||||||
throw new CoreCanceledError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return modalData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the download size of a module.
|
* Get the download size of a module.
|
||||||
*
|
*
|
||||||
|
@ -155,7 +135,13 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
|
||||||
throw new CoreError(accessInfo.preventaccessreasons[0].message);
|
throw new CoreError(accessInfo.preventaccessreasons[0].message);
|
||||||
}
|
}
|
||||||
|
|
||||||
password = await this.askUserPassword();
|
// Create and show the modal.
|
||||||
|
const response = await CoreDomUtils.promptPassword({
|
||||||
|
title: 'addon.mod_lesson.enterpassword',
|
||||||
|
placeholder: 'core.login.password',
|
||||||
|
submit: 'addon.mod_lesson.continue',
|
||||||
|
});
|
||||||
|
password = response.password;
|
||||||
|
|
||||||
return this.validatePassword(lessonId, accessInfo, password, options);
|
return this.validatePassword(lessonId, accessInfo, password, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
import {
|
import {
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
|
CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
|
||||||
import { CoreCourses } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
@ -103,7 +103,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.title = Translate.instant('core.sitehome.sitehome');
|
this.title = Translate.instant('core.sitehome.sitehome');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest') ??
|
||||||
|
(await CoreCourseHelper.courseUsesGuestAccessInfo(this.courseId)).guestAccess;
|
||||||
|
|
||||||
this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
||||||
|
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
@ -657,6 +659,25 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async getCourse(courseId: number): Promise<CoreCourseAnyCourseData | undefined> {
|
||||||
|
try {
|
||||||
|
// Check if user is enrolled. If enrolled, no guest access.
|
||||||
|
return await CoreCourses.getUserCourse(courseId, true);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
||||||
|
return await CoreCourses.getCourse(courseId);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CoreCourses.getCourseByField('id', this.courseId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefetch the whole course.
|
* Prefetch the whole course.
|
||||||
*
|
*
|
||||||
|
@ -666,12 +687,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const courses = await CoreCourses.getUserCourses(true);
|
const course = await this.getCourse(this.courseId);
|
||||||
let course = courses.find((course) => course.id == this.courseId);
|
|
||||||
if (!course) {
|
|
||||||
course = await CoreCourses.getCourse(this.courseId);
|
|
||||||
}
|
|
||||||
if (!course) {
|
if (!course) {
|
||||||
|
CoreDomUtils.showErrorModal('core.course.errordownloadingcourse', true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>
|
||||||
|
<h1>{{ title | translate }}</h1>
|
||||||
|
</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<form (ngSubmit)="submitPassword($event)" #passwordForm>
|
||||||
|
<div>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label class="sr-only">{{ placeholder | translate }}</ion-label>
|
||||||
|
<core-show-password name="password">
|
||||||
|
<ion-input class="ion-text-wrap core-ioninput-password" name="password" type="password"
|
||||||
|
placeholder="{{ placeholder | translate }}" [(ngModel)]="password" core-auto-focus [clearOnEdit]="false">
|
||||||
|
</ion-input>
|
||||||
|
</core-show-password>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="error" class="ion-text-wrap ion-padding-top text-danger">
|
||||||
|
<core-format-text [text]="error | translate"></core-format-text>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
<ion-button expand="block" type="submit" [disabled]="!password">
|
||||||
|
{{ submit | translate }}
|
||||||
|
</ion-button>
|
||||||
|
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
|
||||||
|
<input type="submit" class="core-submit-hidden-enter" />
|
||||||
|
</form>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,29 @@
|
||||||
|
// (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 { CorePasswordModalComponent } from './password-modal';
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
|
export { CorePasswordModalComponent };
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CorePasswordModalComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CorePasswordModalModule {}
|
|
@ -0,0 +1,111 @@
|
||||||
|
// (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, ViewChild, ElementRef, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreForms } from '@singletons/form';
|
||||||
|
import { ModalController } from '@singletons';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal that asks the password.
|
||||||
|
*
|
||||||
|
* WARNING: This component is not loaded with components.module.ts.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-password-modal',
|
||||||
|
templateUrl: 'password-modal.html',
|
||||||
|
})
|
||||||
|
export class CorePasswordModalComponent {
|
||||||
|
|
||||||
|
@ViewChild('passwordForm') formElement?: ElementRef;
|
||||||
|
|
||||||
|
@Input() title = 'core.login.password'; // Translatable string to be shown on modal title.
|
||||||
|
@Input() placeholder = 'core.login.password'; // Translatable string to be shown on password input as placeholder.
|
||||||
|
@Input() submit = 'core.submit'; // Translatable string to be shown on submit button.
|
||||||
|
@Input() validator?: (password?: string) => Promise<CorePasswordModalResponse>; // Function to validate the password.
|
||||||
|
|
||||||
|
password = ''; // Previous entered password.
|
||||||
|
error?: string; // Error message to be shown.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the password back.
|
||||||
|
*
|
||||||
|
* @param e Event.
|
||||||
|
*/
|
||||||
|
async submitPassword(e: Event): Promise<void> {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
const response = await this.validatePassword(this.password);
|
||||||
|
|
||||||
|
if (response.validated === undefined) {
|
||||||
|
ModalController.dismiss(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.validated) {
|
||||||
|
ModalController.dismiss(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error = response.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the entered password if validator is available.
|
||||||
|
*
|
||||||
|
* @param password Entered password.
|
||||||
|
* @returns Response of the modal.
|
||||||
|
*/
|
||||||
|
protected async validatePassword(password: string): Promise<CorePasswordModalResponse> {
|
||||||
|
const response: CorePasswordModalResponse = { password };
|
||||||
|
|
||||||
|
if (!this.validator) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = await CoreDomUtils.showModalLoading('core.loading', true);
|
||||||
|
try {
|
||||||
|
return await this.validator(password);
|
||||||
|
} catch (error) {
|
||||||
|
response.validated = false;
|
||||||
|
response.error = error;
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CorePasswordModalParams = Partial<Pick<CorePasswordModalComponent, 'title' | 'placeholder' | 'submit' | 'validator'>>;
|
||||||
|
|
||||||
|
export type CorePasswordModalResponse = {
|
||||||
|
password: string;
|
||||||
|
validated?: boolean;
|
||||||
|
error?: string;
|
||||||
|
};
|
|
@ -75,6 +75,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() initialSectionId?: number; // The section to load first (by ID).
|
@Input() initialSectionId?: number; // The section to load first (by ID).
|
||||||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||||
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
||||||
|
@Input() isGuest?: boolean; // If user is accessing as a guest.
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent<any>>;
|
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent<any>>;
|
||||||
|
@ -462,6 +463,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
params: {
|
params: {
|
||||||
title: this.course.fullname,
|
title: this.course.fullname,
|
||||||
sectionId: selectedId,
|
sectionId: selectedId,
|
||||||
|
isGuest: this.isGuest,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,6 +35,9 @@
|
||||||
"errordownloadingsection": "Error downloading section.",
|
"errordownloadingsection": "Error downloading section.",
|
||||||
"errorgetmodule": "Error getting activity data.",
|
"errorgetmodule": "Error getting activity data.",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"guestaccess_passwordinvalid": "Incorrect access password, please try again",
|
||||||
|
"guestaccess_withpassword": "Guest access requires password",
|
||||||
|
"guestaccess": "Guest access",
|
||||||
"hiddenfromstudents": "Hidden from students",
|
"hiddenfromstudents": "Hidden from students",
|
||||||
"hiddenoncoursepage": "Available but not shown on course page",
|
"hiddenoncoursepage": "Available but not shown on course page",
|
||||||
"highlighted": "Highlighted",
|
"highlighted": "Highlighted",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<core-loading [hideUntil]="dataLoaded && !updatingData">
|
<core-loading [hideUntil]="dataLoaded && !updatingData">
|
||||||
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
||||||
[moduleId]="moduleId" class="core-course-format-{{course.format}}" *ngIf="dataLoaded && sections">
|
[moduleId]="moduleId" class="core-course-format-{{course.format}}" *ngIf="dataLoaded && sections" [isGuest]="isGuest">
|
||||||
</core-course-format>
|
</core-course-format>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -64,6 +64,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
moduleId?: number;
|
moduleId?: number;
|
||||||
displayEnableDownload = false;
|
displayEnableDownload = false;
|
||||||
displayRefresher = false;
|
displayRefresher = false;
|
||||||
|
isGuest?: boolean;
|
||||||
|
|
||||||
protected formatOptions?: Record<string, unknown>;
|
protected formatOptions?: Record<string, unknown>;
|
||||||
protected completionObserver?: CoreEventObserver;
|
protected completionObserver?: CoreEventObserver;
|
||||||
|
@ -92,6 +93,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
this.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
this.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
||||||
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
|
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
|
||||||
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
||||||
|
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
|
||||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||||
if (this.modulesHaveCompletion) {
|
if (this.modulesHaveCompletion) {
|
||||||
|
|
|
@ -151,6 +151,13 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card class="core-info-card ion-text-wrap" *ngIf="!isEnrolled && useGuestAccess && guestAccessPasswordRequired">
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon name="fas-key" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>{{ 'core.course.guestaccess_withpassword' | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<ion-button (click)="openCourse()" *ngIf="!isModal && canAccessCourse" expand="block" fill="outline" class="ion-text-wrap">
|
<ion-button (click)="openCourse()" *ngIf="!isModal && canAccessCourse" expand="block" fill="outline" class="ion-text-wrap">
|
||||||
<ion-icon name="fas-eye" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-eye" slot="start" aria-hidden="true"></ion-icon>
|
||||||
{{ 'core.course.viewcourse' | translate }}
|
{{ 'core.course.viewcourse' | translate }}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import {
|
||||||
} from '@features/course/services/course-options-delegate';
|
} from '@features/course/services/course-options-delegate';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
import { ActionSheetController, ModalController, NgZone, Translate } from '@singletons';
|
import { ActionSheetController, ModalController, NgZone, Translate } from '@singletons';
|
||||||
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper';
|
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper';
|
||||||
|
@ -40,6 +39,8 @@ import { CoreColors } from '@singletons/colors';
|
||||||
import { CorePath } from '@singletons/path';
|
import { CorePath } from '@singletons/path';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
import { CorePlatform } from '@services/platform';
|
import { CorePlatform } from '@services/platform';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CorePasswordModalResponse } from '@components/password-modal/password-modal';
|
||||||
|
|
||||||
const ENROL_BROWSER_METHODS = ['fee', 'paypal'];
|
const ENROL_BROWSER_METHODS = ['fee', 'paypal'];
|
||||||
|
|
||||||
|
@ -65,15 +66,13 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
isModal = false;
|
isModal = false;
|
||||||
contactsExpanded = false;
|
contactsExpanded = false;
|
||||||
|
useGuestAccess = false;
|
||||||
|
guestAccessPasswordRequired = false;
|
||||||
courseUrl = '';
|
courseUrl = '';
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
|
||||||
protected actionSheet?: HTMLIonActionSheetElement;
|
|
||||||
|
|
||||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
|
|
||||||
protected useGuestAccess = false;
|
protected actionSheet?: HTMLIonActionSheetElement;
|
||||||
protected guestInstanceId = new CorePromisedValue<number | undefined>();
|
protected guestInstanceId = new CorePromisedValue<number | undefined>();
|
||||||
protected courseData = new CorePromisedValue<CoreCourseSummaryData | undefined>();
|
protected courseData = new CorePromisedValue<CoreCourseSummaryData | undefined>();
|
||||||
protected waitStart = 0;
|
protected waitStart = 0;
|
||||||
|
@ -142,8 +141,10 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
const info = await CoreCourses.getCourseGuestEnrolmentInfo(guestInstanceId);
|
const info = await CoreCourses.getCourseGuestEnrolmentInfo(guestInstanceId);
|
||||||
|
|
||||||
// Guest access with password is not supported by the app.
|
// Don't allow guest access if it requires a password if not supported.
|
||||||
return !!info.status && !info.passwordrequired;
|
this.guestAccessPasswordRequired = info.passwordrequired;
|
||||||
|
|
||||||
|
return info.status === true && (!info.passwordrequired || CoreCourses.isValidateGuestAccessPasswordAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,11 +289,49 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
*
|
*
|
||||||
* @param replaceCurrentPage If current place should be replaced in the navigation stack.
|
* @param replaceCurrentPage If current place should be replaced in the navigation stack.
|
||||||
*/
|
*/
|
||||||
openCourse(replaceCurrentPage = false): void {
|
async openCourse(replaceCurrentPage = false): Promise<void> {
|
||||||
if (!this.canAccessCourse || !this.course || this.isModal) {
|
if (!this.canAccessCourse || !this.course || this.isModal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const guestInstanceId = await this.guestInstanceId;
|
||||||
|
if (this.useGuestAccess && this.guestAccessPasswordRequired && guestInstanceId) {
|
||||||
|
// Check if the user has access to the course as guest with a previous sent password.
|
||||||
|
let validated = await CoreUtils.promiseWorks(
|
||||||
|
CoreCourse.getSections(this.courseId, true, true, undefined, undefined, false),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!validated) {
|
||||||
|
try {
|
||||||
|
const validatePassword = async (password: string): Promise<CorePasswordModalResponse> => {
|
||||||
|
const response = await CoreCourses.validateGuestAccessPassword(guestInstanceId, password);
|
||||||
|
|
||||||
|
validated = response.validated;
|
||||||
|
let error = response.hint;
|
||||||
|
if (!validated && !error) {
|
||||||
|
error = 'core.course.guestaccess_passwordinvalid';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
password, validated, error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await CoreDomUtils.promptPassword({
|
||||||
|
title: 'core.course.guestaccess',
|
||||||
|
validator: validatePassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.validated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Cancelled, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CoreCourseHelper.openCourse(this.course, { params: { isGuest: this.useGuestAccess }, replace: replaceCurrentPage });
|
CoreCourseHelper.openCourse(this.course, { params: { isGuest: this.useGuestAccess }, replace: replaceCurrentPage });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,16 +381,59 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
* Self enrol in a course.
|
* Self enrol in a course.
|
||||||
*
|
*
|
||||||
* @param instanceId The instance ID.
|
* @param instanceId The instance ID.
|
||||||
* @param password Password to use.
|
|
||||||
* @returns Promise resolved when self enrolled.
|
* @returns Promise resolved when self enrolled.
|
||||||
*/
|
*/
|
||||||
async selfEnrolInCourse(instanceId: number, password = ''): Promise<void> {
|
async selfEnrolInCourse(instanceId: number): Promise<void> {
|
||||||
|
const validatePassword = async (password = ''): Promise<CorePasswordModalResponse> => {
|
||||||
|
const response: CorePasswordModalResponse = {
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
response.validated = await CoreCourses.selfEnrol(this.courseId, password, instanceId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.errorcode === CoreCoursesProvider.ENROL_INVALID_KEY) {
|
||||||
|
response.validated = false;
|
||||||
|
response.error = error.message;
|
||||||
|
} else {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorselfenrol', true);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
const modal = await CoreDomUtils.showModalLoading('core.loading', true);
|
const modal = await CoreDomUtils.showModalLoading('core.loading', true);
|
||||||
|
let response: CorePasswordModalResponse | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreCourses.selfEnrol(this.courseId, password, instanceId);
|
response = await validatePassword();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
// Close modal and refresh data.
|
if (!response.validated) {
|
||||||
|
try {
|
||||||
|
const response = await CoreDomUtils.promptPassword({
|
||||||
|
validator: validatePassword,
|
||||||
|
title: 'core.courses.selfenrolment',
|
||||||
|
placeholder: 'core.courses.password',
|
||||||
|
submit: 'core.courses.enrolme',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.validated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Cancelled, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data.
|
||||||
this.isEnrolled = true;
|
this.isEnrolled = true;
|
||||||
this.dataLoaded = false;
|
this.dataLoaded = false;
|
||||||
|
|
||||||
|
@ -368,35 +450,6 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openCourse(true);
|
this.openCourse(true);
|
||||||
|
|
||||||
modal?.dismiss();
|
|
||||||
} catch (error) {
|
|
||||||
modal?.dismiss();
|
|
||||||
|
|
||||||
if (error && error.errorcode === CoreCoursesProvider.ENROL_INVALID_KEY) {
|
|
||||||
// Initialize the self enrol modal.
|
|
||||||
// Invalid password, show the modal to enter the password.
|
|
||||||
const modalData = await CoreDomUtils.openModal<string>(
|
|
||||||
{
|
|
||||||
component: CoreCoursesSelfEnrolPasswordComponent,
|
|
||||||
componentProps: { password },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modalData !== undefined) {
|
|
||||||
this.selfEnrolInCourse(instanceId, modalData);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
// No password entered, don't show error.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorselfenrol', true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -143,7 +143,9 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
|
this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
|
||||||
this.module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
this.module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
||||||
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest') ??
|
||||||
|
(!!this.course && (await CoreCourseHelper.courseUsesGuestAccessInfo(this.course.id)).guestAccess);
|
||||||
|
|
||||||
this.modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
|
this.modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
|
||||||
this.openModule = CoreNavigator.getRouteBooleanParam('openModule') ?? true; // If false, just scroll to module.
|
this.openModule = CoreNavigator.getRouteBooleanParam('openModule') ?? true; // If false, just scroll to module.
|
||||||
if (!this.modNavOptions) {
|
if (!this.modNavOptions) {
|
||||||
|
|
|
@ -469,12 +469,6 @@ export class CoreCourseHelperProvider {
|
||||||
let handlers: CoreCourseOptionsHandlerToDisplay[] = [];
|
let handlers: CoreCourseOptionsHandlerToDisplay[] = [];
|
||||||
let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
let success = true;
|
let success = true;
|
||||||
let isGuest = false;
|
|
||||||
|
|
||||||
if (options.canHaveGuestCourses) {
|
|
||||||
// Check if the user can only access as guest.
|
|
||||||
isGuest = await this.courseUsesGuestAccess(course.id, siteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the sections and the handlers.
|
// Get the sections and the handlers.
|
||||||
subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => {
|
subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => {
|
||||||
|
@ -483,12 +477,12 @@ export class CoreCourseHelperProvider {
|
||||||
return;
|
return;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, isGuest).then((cHandlers) => {
|
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false).then((cHandlers) => {
|
||||||
handlers = cHandlers;
|
handlers = cHandlers;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}));
|
}));
|
||||||
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, isGuest).then((mHandlers) => {
|
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false).then((mHandlers) => {
|
||||||
menuHandlers = mHandlers;
|
menuHandlers = mHandlers;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -598,19 +592,22 @@ export class CoreCourseHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a course is accessed using guest access.
|
* Check whether a course is accessed using guest access and if it requires password to enter.
|
||||||
*
|
*
|
||||||
* @param courseId Course ID.
|
* @param courseId Course ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @returns Promise resolved with boolean: whether course is accessed using guest access.
|
* @returns Promise resolved with guestAccess and passwordRequired booleans.
|
||||||
*/
|
*/
|
||||||
async courseUsesGuestAccess(courseId: number, siteId?: string): Promise<boolean> {
|
async courseUsesGuestAccessInfo(
|
||||||
|
courseId: number,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<{guestAccess: boolean; passwordRequired?: boolean}> {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
// Check if user is enrolled. If enrolled, no guest access.
|
// Check if user is enrolled. If enrolled, no guest access.
|
||||||
await CoreCourses.getUserCourse(courseId, false, siteId);
|
await CoreCourses.getUserCourse(courseId, false, siteId);
|
||||||
|
|
||||||
return false;
|
return { guestAccess: false };
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
@ -619,7 +616,7 @@ export class CoreCourseHelperProvider {
|
||||||
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
|
||||||
await CoreCourses.getCourse(courseId, siteId);
|
await CoreCourses.getCourse(courseId, siteId);
|
||||||
|
|
||||||
return false;
|
return { guestAccess: false };
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
@ -630,19 +627,19 @@ export class CoreCourseHelperProvider {
|
||||||
const method = enrolmentMethods.find((method) => method.type === 'guest');
|
const method = enrolmentMethods.find((method) => method.type === 'guest');
|
||||||
|
|
||||||
if (!method) {
|
if (!method) {
|
||||||
return false;
|
return { guestAccess: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = await CoreCourses.getCourseGuestEnrolmentInfo(method.id);
|
const info = await CoreCourses.getCourseGuestEnrolmentInfo(method.id);
|
||||||
if (!info.status) {
|
|
||||||
// Not active, reject.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't allow guest access if it requires a password.
|
// Don't allow guest access if it requires a password and it's available.
|
||||||
return !info.passwordrequired;
|
return {
|
||||||
|
guestAccess: info.status === true &&
|
||||||
|
(!info.passwordrequired || CoreCourses.isValidateGuestAccessPasswordAvailable()),
|
||||||
|
passwordRequired: info.passwordrequired,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return { guestAccess: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1215,20 +1212,17 @@ export class CoreCourseHelperProvider {
|
||||||
*
|
*
|
||||||
* @param courses Courses array to prefetch.
|
* @param courses Courses array to prefetch.
|
||||||
* @param prefetch Prefetch information to be updated.
|
* @param prefetch Prefetch information to be updated.
|
||||||
* @param options Other options.
|
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async prefetchCourses(
|
async prefetchCourses(
|
||||||
courses: CoreCourseAnyCourseData[],
|
courses: CoreCourseAnyCourseData[],
|
||||||
prefetch: CorePrefetchStatusInfo,
|
prefetch: CorePrefetchStatusInfo,
|
||||||
options: CoreCoursePrefetchCoursesOptions = {},
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
prefetch.loading = true;
|
prefetch.loading = true;
|
||||||
prefetch.icon = CoreConstants.ICON_DOWNLOADING;
|
prefetch.icon = CoreConstants.ICON_DOWNLOADING;
|
||||||
prefetch.badge = '';
|
prefetch.badge = '';
|
||||||
|
|
||||||
const prefetchOptions = {
|
const prefetchOptions = {
|
||||||
...options,
|
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
prefetch.badge = progress.count + ' / ' + progress.total;
|
prefetch.badge = progress.count + ' / ' + progress.total;
|
||||||
prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress);
|
prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress);
|
||||||
|
@ -2143,17 +2137,10 @@ export type CoreCoursePrefetchCourseOptions = {
|
||||||
isGuest?: boolean; // Whether the user is guest.
|
isGuest?: boolean; // Whether the user is guest.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for prefetch courses function.
|
|
||||||
*/
|
|
||||||
export type CoreCoursePrefetchCoursesOptions = {
|
|
||||||
canHaveGuestCourses?: boolean; // Whether the list of courses can contain courses with only guest access.
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for confirm and prefetch courses function.
|
* Options for confirm and prefetch courses function.
|
||||||
*/
|
*/
|
||||||
export type CoreCourseConfirmPrefetchCoursesOptions = CoreCoursePrefetchCoursesOptions & {
|
export type CoreCourseConfirmPrefetchCoursesOptions = {
|
||||||
onProgress?: (data: CoreCourseCoursesProgress) => void;
|
onProgress?: (data: CoreCourseCoursesProgress) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -442,41 +442,6 @@ Feature: Test basic usage of one course in app
|
||||||
And I should find "Test scorm name" in the app
|
And I should find "Test scorm name" in the app
|
||||||
And I should find "Test workshop name" in the app
|
And I should find "Test workshop name" in the app
|
||||||
|
|
||||||
@lms_from4.0
|
|
||||||
Scenario: Guest access
|
|
||||||
Given I entered the course "Course 1" as "teacher1" in the app
|
|
||||||
And I press "Course summary" in the app
|
|
||||||
And I press "Open in browser" in the app
|
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I click on "Participants" "link"
|
|
||||||
And I select "Enrolment methods" from the "jump" singleselect
|
|
||||||
And I click on "Enable" "icon" in the "Guest access" "table_row"
|
|
||||||
And I close the browser tab opened by the app
|
|
||||||
Given I entered the app as "student2"
|
|
||||||
When I press "Site home" in the app
|
|
||||||
And I press "Available courses" in the app
|
|
||||||
And I press "Course 1" in the app
|
|
||||||
|
|
||||||
Then I should find "Course summary" in the app
|
|
||||||
And I should find "Course" in the app
|
|
||||||
|
|
||||||
When I press "View course" "ion-button" in the app
|
|
||||||
Then the header should be "Course 1" in the app
|
|
||||||
And I should find "Choice course 1" in the app
|
|
||||||
And I should find "assignment" in the app
|
|
||||||
And I should find "Test forum name" in the app
|
|
||||||
And I should find "Test chat name" in the app
|
|
||||||
And I should find "Web links" in the app
|
|
||||||
And I should find "Test feedback name" in the app
|
|
||||||
And I should find "Test glossary" in the app
|
|
||||||
And I should find "Quiz 1" in the app
|
|
||||||
And I should find "Test survey name" in the app
|
|
||||||
And I should find "Test wiki name" in the app
|
|
||||||
And I should find "Test lesson name" in the app
|
|
||||||
And I should find "Test scorm name" in the app
|
|
||||||
And I should find "Test workshop name" in the app
|
|
||||||
|
|
||||||
Scenario: View blocks on drawer
|
Scenario: View blocks on drawer
|
||||||
Given the following "blocks" exist:
|
Given the following "blocks" exist:
|
||||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion | configdata |
|
| blockname | contextlevel | reference | pagetypepattern | defaultregion | configdata |
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
@core @core_course @app @javascript @enrol @enrol_guest
|
||||||
|
Feature: Test basic usage of guest access course in app
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname | email |
|
||||||
|
| teacher1 | Teacher | teacher | teacher1@example.com |
|
||||||
|
| student1 | Student | student | student1@example.com |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname | category |
|
||||||
|
| Course 1 | C1 | 0 |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| teacher1 | C1 | editingteacher |
|
||||||
|
And the following "activities" exist:
|
||||||
|
| activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | section |
|
||||||
|
| assign | C1 | assign1 | assignment | Test assignment description | 1 | 1 |
|
||||||
|
And the following "activities" exist:
|
||||||
|
| activity | name | intro | course | idnumber | groupmode |
|
||||||
|
| wiki | Test wiki name | Test wiki | C1 | wiki | 0 |
|
||||||
|
|
||||||
|
|
||||||
|
@lms_from4.0
|
||||||
|
Scenario: Guest access without password (student)
|
||||||
|
Given I log in as "teacher1"
|
||||||
|
And I am on the "Course 1" "enrolment methods" page
|
||||||
|
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| Allow guest access | Yes |
|
||||||
|
And I press "Save changes"
|
||||||
|
And I entered the app as "student1"
|
||||||
|
|
||||||
|
When I press "Site home" in the app
|
||||||
|
And I press "Available courses" in the app
|
||||||
|
And I press "Course 1" in the app
|
||||||
|
Then I should find "Course summary" in the app
|
||||||
|
And I should find "Course" in the app
|
||||||
|
|
||||||
|
When I press "View course" "ion-button" in the app
|
||||||
|
Then the header should be "Course 1" in the app
|
||||||
|
And I should find "assignment" in the app
|
||||||
|
And I should find "Test wiki name" in the app
|
||||||
|
|
||||||
|
When I press "assignment" in the app
|
||||||
|
Then I should not find "Add submission" in the app
|
||||||
|
|
||||||
|
@lms_from4.3
|
||||||
|
Scenario: Guest access with password (student)
|
||||||
|
Given I log in as "teacher1"
|
||||||
|
And I am on the "Course 1" "enrolment methods" page
|
||||||
|
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||||
|
And I set the following fields to these values:
|
||||||
|
| Allow guest access | Yes |
|
||||||
|
| Password | moodle_rules |
|
||||||
|
And I press "Save changes"
|
||||||
|
And I entered the app as "student1"
|
||||||
|
|
||||||
|
When I press "Site home" in the app
|
||||||
|
And I press "Available courses" in the app
|
||||||
|
And I press "Course 1" in the app
|
||||||
|
Then I should find "Course summary" in the app
|
||||||
|
And I should find "Course" in the app
|
||||||
|
And I should find "Guest access requires password" in the app
|
||||||
|
|
||||||
|
When I press "View course" "ion-button" in the app
|
||||||
|
And I set the following fields to these values in the app:
|
||||||
|
| Password | wrong |
|
||||||
|
And I press "Submit" "ion-button" in the app
|
||||||
|
Then I should find "Incorrect access password, please try again" in the app
|
||||||
|
|
||||||
|
# Show the hint.
|
||||||
|
Given the following config values are set as admin:
|
||||||
|
| showhint | 1 | enrol_guest |
|
||||||
|
When I press "Submit" "ion-button" in the app
|
||||||
|
Then I should find "That access password was incorrect, please try again" in the app
|
||||||
|
When I set the following fields to these values in the app:
|
||||||
|
| Password | moodle_rules |
|
||||||
|
And I press "Submit" "ion-button" in the app
|
||||||
|
Then the header should be "Course 1" in the app
|
||||||
|
And I should find "assignment" in the app
|
||||||
|
And I should find "Test wiki name" in the app
|
||||||
|
|
||||||
|
When I press "assignment" in the app
|
||||||
|
Then I should not find "Add submission" in the app
|
|
@ -18,14 +18,12 @@ import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item';
|
import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item';
|
||||||
import { CoreCoursesCourseProgressComponent } from './course-progress/course-progress';
|
import { CoreCoursesCourseProgressComponent } from './course-progress/course-progress';
|
||||||
import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu';
|
import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu';
|
||||||
import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/self-enrol-password';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreCoursesCourseListItemComponent,
|
CoreCoursesCourseListItemComponent,
|
||||||
CoreCoursesCourseProgressComponent,
|
CoreCoursesCourseProgressComponent,
|
||||||
CoreCoursesCourseOptionsMenuComponent,
|
CoreCoursesCourseOptionsMenuComponent,
|
||||||
CoreCoursesSelfEnrolPasswordComponent,
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
|
@ -34,7 +32,6 @@ import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/sel
|
||||||
CoreCoursesCourseListItemComponent,
|
CoreCoursesCourseListItemComponent,
|
||||||
CoreCoursesCourseProgressComponent,
|
CoreCoursesCourseProgressComponent,
|
||||||
CoreCoursesCourseOptionsMenuComponent,
|
CoreCoursesCourseOptionsMenuComponent,
|
||||||
CoreCoursesSelfEnrolPasswordComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreCoursesComponentsModule {}
|
export class CoreCoursesComponentsModule {}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>
|
|
||||||
<h1>{{ 'core.courses.selfenrolment' | translate }}</h1>
|
|
||||||
</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button fill="clear" (click)="close()" [attr.aria-label]="'core.close' | translate">
|
|
||||||
<ion-icon name="fas-xmark" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
<ion-content class="ion-padding">
|
|
||||||
<form (ngSubmit)="submitPassword($event)" #enrolPasswordForm>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label class="sr-only">{{ 'core.courses.password' | translate }}</ion-label>
|
|
||||||
<core-show-password name="password">
|
|
||||||
<ion-input class="ion-text-wrap core-ioninput-password" name="password" type="password"
|
|
||||||
placeholder="{{ 'core.courses.password' | translate }}" [(ngModel)]="password" core-auto-focus [clearOnEdit]="false">
|
|
||||||
</ion-input>
|
|
||||||
</core-show-password>
|
|
||||||
</ion-item>
|
|
||||||
<div class="ion-padding">
|
|
||||||
<ion-button expand="block" [disabled]="!password" type="submit">{{ 'core.courses.enrolme' | translate }}</ion-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ion-content>
|
|
|
@ -1,62 +0,0 @@
|
||||||
// (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, ViewChild, ElementRef } from '@angular/core';
|
|
||||||
import { NavParams } from '@ionic/angular';
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { ModalController } from '@singletons';
|
|
||||||
import { CoreForms } from '@singletons/form';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal that displays a form to enter a password to self enrol in a course.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'page-core-courses-self-enrol-password',
|
|
||||||
templateUrl: 'self-enrol-password.html',
|
|
||||||
})
|
|
||||||
export class CoreCoursesSelfEnrolPasswordComponent {
|
|
||||||
|
|
||||||
@ViewChild('enrolPasswordForm') formElement!: ElementRef;
|
|
||||||
password = '';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
navParams: NavParams,
|
|
||||||
) {
|
|
||||||
this.password = navParams.get('password') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close help modal.
|
|
||||||
*/
|
|
||||||
close(): void {
|
|
||||||
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
|
||||||
|
|
||||||
ModalController.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit password.
|
|
||||||
*
|
|
||||||
* @param e Event.
|
|
||||||
*/
|
|
||||||
submitPassword(e: Event): void {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
|
||||||
|
|
||||||
ModalController.dismiss(this.password);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -290,7 +290,7 @@ export class CoreCoursesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get course.
|
* Get course information if user has persmissions to view.
|
||||||
*
|
*
|
||||||
* @param id ID of the course to get.
|
* @param id ID of the course to get.
|
||||||
* @param siteId Site to get the courses from. If not defined, use current site.
|
* @param siteId Site to get the courses from. If not defined, use current site.
|
||||||
|
@ -324,7 +324,8 @@ export class CoreCoursesProvider {
|
||||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||||
};
|
};
|
||||||
|
|
||||||
return site.read('core_enrol_get_course_enrolment_methods', params, preSets);
|
return site.read<CoreEnrolGetCourseEnrolmentMethodsWSResponse>
|
||||||
|
('core_enrol_get_course_enrolment_methods', params, preSets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -368,6 +369,47 @@ export class CoreCoursesProvider {
|
||||||
return ROOT_CACHE_KEY + 'guestinfo:' + instanceId;
|
return ROOT_CACHE_KEY + 'guestinfo:' + instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if guest password validation WS is available on the current site.
|
||||||
|
*
|
||||||
|
* @returns Whether guest password validation WSget courses by field is available.
|
||||||
|
*/
|
||||||
|
isValidateGuestAccessPasswordAvailable(): boolean {
|
||||||
|
return CoreSites.wsAvailableInCurrentSite('enrol_guest_validate_password');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform password validation of guess access.
|
||||||
|
*
|
||||||
|
* @param enrolmentInstanceId Instance id of guest enrolment plugin.
|
||||||
|
* @param password Course Password.
|
||||||
|
* @returns Wether the password is valid.
|
||||||
|
*/
|
||||||
|
async validateGuestAccessPassword(
|
||||||
|
enrolmentInstanceId: number,
|
||||||
|
password: string,
|
||||||
|
): Promise<EnrolGuestValidatePasswordWSResponse> {
|
||||||
|
const site = CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return {
|
||||||
|
validated: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const preSets: CoreSiteWSPreSets = {
|
||||||
|
getFromCache: false,
|
||||||
|
saveToCache: false,
|
||||||
|
emergencyCache: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params: EnrolGuestValidatePasswordWSParams = {
|
||||||
|
instanceid: enrolmentInstanceId,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await site.read<EnrolGuestValidatePasswordWSResponse>('enrol_guest_validate_password', params, preSets);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get courses.
|
* Get courses.
|
||||||
* Warning: if the user doesn't have permissions to view some of the courses passed the WS call will fail.
|
* Warning: if the user doesn't have permissions to view some of the courses passed the WS call will fail.
|
||||||
|
@ -1773,14 +1815,25 @@ type CoreEnrolGetCourseEnrolmentMethodsWSParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Course enrolment method.
|
* Data returned by core_enrol_get_course_enrolment_methods WS.
|
||||||
*/
|
*/
|
||||||
export type CoreCourseEnrolmentMethod = {
|
type CoreEnrolGetCourseEnrolmentMethodsWSResponse = CoreCourseEnrolmentMethod[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course enrolment basic info.
|
||||||
|
*/
|
||||||
|
export type CoreCourseEnrolmentInfo = {
|
||||||
id: number; // Id of course enrolment instance.
|
id: number; // Id of course enrolment instance.
|
||||||
courseid: number; // Id of course.
|
courseid: number; // Id of course.
|
||||||
type: string; // Type of enrolment plugin.
|
type: string; // Type of enrolment plugin.
|
||||||
name: string; // Name of enrolment plugin.
|
name: string; // Name of enrolment plugin.
|
||||||
status: string; // Status of enrolment plugin.
|
status: boolean | string; // Available status of enrolment plugin. True if successful, else error message or false.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course enrolment method.
|
||||||
|
*/
|
||||||
|
export type CoreCourseEnrolmentMethod = CoreCourseEnrolmentInfo & {
|
||||||
wsfunction?: string; // Webservice function to get more information.
|
wsfunction?: string; // Webservice function to get more information.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1822,8 +1875,8 @@ export type CoreCourseGetRecentCoursesOptions = CoreSitesCommonWSOptions & {
|
||||||
/**
|
/**
|
||||||
* Course guest enrolment method.
|
* Course guest enrolment method.
|
||||||
*/
|
*/
|
||||||
export type CoreCourseEnrolmentGuestMethod = CoreCourseEnrolmentMethod & {
|
export type CoreCourseEnrolmentGuestMethod = CoreCourseEnrolmentInfo & {
|
||||||
passwordrequired: boolean; // Is a password required?.
|
passwordrequired: boolean; // Is a password required?
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1857,3 +1910,20 @@ export type CoreCourseAnyCourseDataWithOptions = CoreCourseAnyCourseData & {
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of enrol_guest_validate_password WS.
|
||||||
|
*/
|
||||||
|
type EnrolGuestValidatePasswordWSParams = {
|
||||||
|
instanceid: number; // instance id of guest enrolment plugin
|
||||||
|
password: string; // the course password
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by enrol_guest_get_instance_info WS.
|
||||||
|
*/
|
||||||
|
export type EnrolGuestValidatePasswordWSResponse = {
|
||||||
|
validated: boolean; // Whether the password was successfully validated
|
||||||
|
hint?: string; // Password hint (if enabled)
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
|
@ -59,6 +59,7 @@ import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
||||||
import { CorePlatform } from '@services/platform';
|
import { CorePlatform } from '@services/platform';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreLang } from '@services/lang';
|
import { CoreLang } from '@services/lang';
|
||||||
|
import { CorePasswordModalParams, CorePasswordModalResponse } from '@components/password-modal/password-modal';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||||
|
@ -1880,6 +1881,33 @@ export class CoreDomUtilsProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts password to the user and returns the entered text.
|
||||||
|
*
|
||||||
|
* @param passwordParams Params to show the modal.
|
||||||
|
* @returns Entered password, error and validation.
|
||||||
|
*/
|
||||||
|
async promptPassword(passwordParams?: CorePasswordModalParams): Promise<CorePasswordModalResponse> {
|
||||||
|
const { CorePasswordModalComponent } =
|
||||||
|
await import('@/core/components/password-modal/password-modal.module');
|
||||||
|
|
||||||
|
const modalData = await CoreDomUtils.openModal<CorePasswordModalResponse>(
|
||||||
|
{
|
||||||
|
cssClass: 'core-password-modal',
|
||||||
|
showBackdrop: true,
|
||||||
|
backdropDismiss: true,
|
||||||
|
component: CorePasswordModalComponent,
|
||||||
|
componentProps: passwordParams,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (modalData === undefined) {
|
||||||
|
throw new CoreCanceledError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return modalData;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View an image in a modal.
|
* View an image in a modal.
|
||||||
*
|
*
|
||||||
|
|
|
@ -729,6 +729,21 @@ body.core-iframe-fullscreen ion-router-outlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-password-modal {
|
||||||
|
--border-radius: var(--medium-radius);
|
||||||
|
--min-width: auto;
|
||||||
|
--min-height: 300px;
|
||||||
|
--width: 384px;
|
||||||
|
--height: auto;
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hidden submit button.
|
// Hidden submit button.
|
||||||
.core-submit-hidden-enter {
|
.core-submit-hidden-enter {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
Loading…
Reference in New Issue