commit
271e012493
|
@ -3,7 +3,7 @@
|
|||
# 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.
|
||||
function download_file {
|
||||
|
@ -12,7 +12,7 @@ function download_file {
|
|||
|
||||
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)
|
||||
if [ ! -n "$filename" ] || [ "$size" -le 1 ]; then
|
||||
echo "Wrong or corrupt file $filename"
|
||||
|
@ -45,7 +45,7 @@ function get_english {
|
|||
get_app_version
|
||||
|
||||
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
|
||||
|
|
|
@ -1582,6 +1582,9 @@
|
|||
"core.course.errordownloadingsection": "local_moodlemobileapp",
|
||||
"core.course.errorgetmodule": "local_moodlemobileapp",
|
||||
"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.hiddenoncoursepage": "moodle",
|
||||
"core.course.highlighted": "moodle",
|
||||
|
|
|
@ -18,13 +18,11 @@ import { CoreSharedModule } from '@/core/shared.module';
|
|||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||
import { AddonModLessonIndexComponent } from './index/index';
|
||||
import { AddonModLessonMenuModalPage } from './menu-modal/menu-modal';
|
||||
import { AddonModLessonPasswordModalComponent } from './password-modal/password-modal';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModLessonIndexComponent,
|
||||
AddonModLessonMenuModalPage,
|
||||
AddonModLessonPasswordModalComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
|
@ -35,7 +33,6 @@ import { AddonModLessonPasswordModalComponent } from './password-modal/password-
|
|||
exports: [
|
||||
AddonModLessonIndexComponent,
|
||||
AddonModLessonMenuModalPage,
|
||||
AddonModLessonPasswordModalComponent,
|
||||
],
|
||||
})
|
||||
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.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
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 { CoreWSFile } from '@services/ws';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { AddonModLessonPasswordModalComponent } from '../../components/password-modal/password-modal';
|
||||
import {
|
||||
AddonModLesson,
|
||||
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.
|
||||
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.
|
||||
*
|
||||
|
@ -155,7 +135,13 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
import {
|
||||
CoreCourseModulePrefetchDelegate,
|
||||
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 { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
@ -103,7 +103,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
|||
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.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
|
@ -657,6 +659,25 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
|||
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.
|
||||
*
|
||||
|
@ -666,12 +687,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
|||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const courses = await CoreCourses.getUserCourses(true);
|
||||
let course = courses.find((course) => course.id == this.courseId);
|
||||
if (!course) {
|
||||
course = await CoreCourses.getCourse(this.courseId);
|
||||
}
|
||||
const course = await this.getCourse(this.courseId);
|
||||
if (!course) {
|
||||
CoreDomUtils.showErrorModal('core.course.errordownloadingcourse', true);
|
||||
|
||||
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() 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() isGuest?: boolean; // If user is accessing as a guest.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent<any>>;
|
||||
|
@ -462,6 +463,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
params: {
|
||||
title: this.course.fullname,
|
||||
sectionId: selectedId,
|
||||
isGuest: this.isGuest,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
"errordownloadingsection": "Error downloading section.",
|
||||
"errorgetmodule": "Error getting activity data.",
|
||||
"failed": "Failed",
|
||||
"guestaccess_passwordinvalid": "Incorrect access password, please try again",
|
||||
"guestaccess_withpassword": "Guest access requires password",
|
||||
"guestaccess": "Guest access",
|
||||
"hiddenfromstudents": "Hidden from students",
|
||||
"hiddenoncoursepage": "Available but not shown on course page",
|
||||
"highlighted": "Highlighted",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<core-loading [hideUntil]="dataLoaded && !updatingData">
|
||||
<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-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -64,6 +64,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
|||
moduleId?: number;
|
||||
displayEnableDownload = false;
|
||||
displayRefresher = false;
|
||||
isGuest?: boolean;
|
||||
|
||||
protected formatOptions?: Record<string, unknown>;
|
||||
protected completionObserver?: CoreEventObserver;
|
||||
|
@ -92,6 +93,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
|||
this.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
||||
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
|
||||
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
||||
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
||||
|
||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||
if (this.modulesHaveCompletion) {
|
||||
|
|
|
@ -151,6 +151,13 @@
|
|||
</ion-item>
|
||||
</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-icon name="fas-eye" slot="start" aria-hidden="true"></ion-icon>
|
||||
{{ 'core.course.viewcourse' | translate }}
|
||||
|
|
|
@ -31,7 +31,6 @@ import {
|
|||
} from '@features/course/services/course-options-delegate';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
import { ActionSheetController, ModalController, NgZone, Translate } from '@singletons';
|
||||
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper';
|
||||
|
@ -40,6 +39,8 @@ import { CoreColors } from '@singletons/colors';
|
|||
import { CorePath } from '@singletons/path';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
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'];
|
||||
|
||||
|
@ -65,15 +66,13 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
dataLoaded = false;
|
||||
isModal = false;
|
||||
contactsExpanded = false;
|
||||
|
||||
useGuestAccess = false;
|
||||
guestAccessPasswordRequired = false;
|
||||
courseUrl = '';
|
||||
progress?: number;
|
||||
|
||||
protected actionSheet?: HTMLIonActionSheetElement;
|
||||
|
||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||
|
||||
protected useGuestAccess = false;
|
||||
protected actionSheet?: HTMLIonActionSheetElement;
|
||||
protected guestInstanceId = new CorePromisedValue<number | undefined>();
|
||||
protected courseData = new CorePromisedValue<CoreCourseSummaryData | undefined>();
|
||||
protected waitStart = 0;
|
||||
|
@ -142,8 +141,10 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
|
||||
const info = await CoreCourses.getCourseGuestEnrolmentInfo(guestInstanceId);
|
||||
|
||||
// Guest access with password is not supported by the app.
|
||||
return !!info.status && !info.passwordrequired;
|
||||
// Don't allow guest access if it requires a password if not supported.
|
||||
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.
|
||||
*/
|
||||
openCourse(replaceCurrentPage = false): void {
|
||||
async openCourse(replaceCurrentPage = false): Promise<void> {
|
||||
if (!this.canAccessCourse || !this.course || this.isModal) {
|
||||
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 });
|
||||
}
|
||||
|
||||
|
@ -342,16 +381,59 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
* Self enrol in a course.
|
||||
*
|
||||
* @param instanceId The instance ID.
|
||||
* @param password Password to use.
|
||||
* @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);
|
||||
let response: CorePasswordModalResponse | undefined;
|
||||
|
||||
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.dataLoaded = false;
|
||||
|
||||
|
@ -368,35 +450,6 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
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.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.openModule = CoreNavigator.getRouteBooleanParam('openModule') ?? true; // If false, just scroll to module.
|
||||
if (!this.modNavOptions) {
|
||||
|
|
|
@ -469,12 +469,6 @@ export class CoreCourseHelperProvider {
|
|||
let handlers: CoreCourseOptionsHandlerToDisplay[] = [];
|
||||
let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||
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.
|
||||
subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => {
|
||||
|
@ -483,12 +477,12 @@ export class CoreCourseHelperProvider {
|
|||
return;
|
||||
}));
|
||||
|
||||
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, isGuest).then((cHandlers) => {
|
||||
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false).then((cHandlers) => {
|
||||
handlers = cHandlers;
|
||||
|
||||
return;
|
||||
}));
|
||||
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, isGuest).then((mHandlers) => {
|
||||
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false).then((mHandlers) => {
|
||||
menuHandlers = mHandlers;
|
||||
|
||||
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 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 {
|
||||
// Check if user is enrolled. If enrolled, no guest access.
|
||||
await CoreCourses.getUserCourse(courseId, false, siteId);
|
||||
|
||||
return false;
|
||||
return { guestAccess: false };
|
||||
} catch {
|
||||
// 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.
|
||||
await CoreCourses.getCourse(courseId, siteId);
|
||||
|
||||
return false;
|
||||
return { guestAccess: false };
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
@ -630,19 +627,19 @@ export class CoreCourseHelperProvider {
|
|||
const method = enrolmentMethods.find((method) => method.type === 'guest');
|
||||
|
||||
if (!method) {
|
||||
return false;
|
||||
return { guestAccess: false };
|
||||
}
|
||||
|
||||
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.
|
||||
return !info.passwordrequired;
|
||||
// Don't allow guest access if it requires a password and it's available.
|
||||
return {
|
||||
guestAccess: info.status === true &&
|
||||
(!info.passwordrequired || CoreCourses.isValidateGuestAccessPasswordAvailable()),
|
||||
passwordRequired: info.passwordrequired,
|
||||
};
|
||||
} catch {
|
||||
return false;
|
||||
return { guestAccess: false };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1215,20 +1212,17 @@ export class CoreCourseHelperProvider {
|
|||
*
|
||||
* @param courses Courses array to prefetch.
|
||||
* @param prefetch Prefetch information to be updated.
|
||||
* @param options Other options.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async prefetchCourses(
|
||||
courses: CoreCourseAnyCourseData[],
|
||||
prefetch: CorePrefetchStatusInfo,
|
||||
options: CoreCoursePrefetchCoursesOptions = {},
|
||||
): Promise<void> {
|
||||
prefetch.loading = true;
|
||||
prefetch.icon = CoreConstants.ICON_DOWNLOADING;
|
||||
prefetch.badge = '';
|
||||
|
||||
const prefetchOptions = {
|
||||
...options,
|
||||
onProgress: (progress) => {
|
||||
prefetch.badge = progress.count + ' / ' + progress.total;
|
||||
prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress);
|
||||
|
@ -2143,17 +2137,10 @@ export type CoreCoursePrefetchCourseOptions = {
|
|||
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.
|
||||
*/
|
||||
export type CoreCourseConfirmPrefetchCoursesOptions = CoreCoursePrefetchCoursesOptions & {
|
||||
export type CoreCourseConfirmPrefetchCoursesOptions = {
|
||||
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 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
|
||||
Given the following "blocks" exist:
|
||||
| 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 { CoreCoursesCourseProgressComponent } from './course-progress/course-progress';
|
||||
import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu';
|
||||
import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/self-enrol-password';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreCoursesCourseListItemComponent,
|
||||
CoreCoursesCourseProgressComponent,
|
||||
CoreCoursesCourseOptionsMenuComponent,
|
||||
CoreCoursesSelfEnrolPasswordComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
|
@ -34,7 +32,6 @@ import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/sel
|
|||
CoreCoursesCourseListItemComponent,
|
||||
CoreCoursesCourseProgressComponent,
|
||||
CoreCoursesCourseOptionsMenuComponent,
|
||||
CoreCoursesSelfEnrolPasswordComponent,
|
||||
],
|
||||
})
|
||||
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 siteId Site to get the courses from. If not defined, use current site.
|
||||
|
@ -324,7 +324,8 @@ export class CoreCoursesProvider {
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
courseid: number; // Id of course.
|
||||
type: string; // Type 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.
|
||||
};
|
||||
|
||||
|
@ -1822,8 +1875,8 @@ export type CoreCourseGetRecentCoursesOptions = CoreSitesCommonWSOptions & {
|
|||
/**
|
||||
* Course guest enrolment method.
|
||||
*/
|
||||
export type CoreCourseEnrolmentGuestMethod = CoreCourseEnrolmentMethod & {
|
||||
passwordrequired: boolean; // Is a password required?.
|
||||
export type CoreCourseEnrolmentGuestMethod = CoreCourseEnrolmentInfo & {
|
||||
passwordrequired: boolean; // Is a password required?
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1857,3 +1910,20 @@ export type CoreCourseAnyCourseDataWithOptions = CoreCourseAnyCourseData & {
|
|||
navOptions?: 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 { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
.core-submit-hidden-enter {
|
||||
position: absolute;
|
||||
|
@ -871,7 +886,7 @@ img.large-avatar,
|
|||
max-width: var(--core-large-avatar-size);
|
||||
max-height: var(--core-large-avatar-size);
|
||||
margin-bottom: 10px;
|
||||
border-radius : 50%;
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--stroke);
|
||||
background-color: transparent;
|
||||
|
|
Loading…
Reference in New Issue