commit
3036d096aa
|
@ -64,7 +64,7 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.badges !== undefined) {
|
if (navOptions?.badges !== undefined) {
|
||||||
return navOptions.badges;
|
return navOptions.badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export class AddonBlogCourseOptionHandlerService implements CoreCourseOptionsHan
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const enabled = await CoreCourseHelper.hasABlockNamed(courseId, 'blog_menu');
|
const enabled = await CoreCourseHelper.hasABlockNamed(courseId, 'blog_menu');
|
||||||
|
|
||||||
if (enabled && navOptions && navOptions.blogs !== undefined) {
|
if (enabled && navOptions?.blogs !== undefined) {
|
||||||
return navOptions.blogs;
|
return navOptions.blogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseAccess,
|
CoreCourseAccess,
|
||||||
CoreCourseOptionsHandler,
|
CoreCourseOptionsHandler,
|
||||||
|
@ -50,11 +50,11 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (accessData && accessData.type === CoreCourseProvider.ACCESS_GUEST) {
|
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||||
return false; // Not enabled for guest access.
|
return false; // Not enabled for guest access.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.competencies !== undefined) {
|
if (navOptions?.competencies !== undefined) {
|
||||||
return navOptions.competencies;
|
return navOptions.competencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
if (navOptions && navOptions.competencies !== undefined) {
|
if (navOptions?.competencies !== undefined) {
|
||||||
// No need to invalidate anything.
|
// No need to invalidate anything.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseAccess,
|
CoreCourseAccess,
|
||||||
CoreCourseOptionsHandler,
|
CoreCourseOptionsHandler,
|
||||||
|
@ -43,7 +43,7 @@ export class AddonCourseCompletionCourseOptionHandlerService implements CoreCour
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async isEnabledForCourse(courseId: number, accessData: CoreCourseAccess): Promise<boolean> {
|
async isEnabledForCourse(courseId: number, accessData: CoreCourseAccess): Promise<boolean> {
|
||||||
if (accessData && accessData.type === CoreCourseProvider.ACCESS_GUEST) {
|
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||||
return false; // Not enabled for guest access.
|
return false; // Not enabled for guest access.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,14 +79,11 @@ Feature: Test basic usage of BBB activity in app
|
||||||
And I should not be able to press "Join session" in the app
|
And I should not be able to press "Join session" in the app
|
||||||
|
|
||||||
# Join the session as moderator in a browser.
|
# Join the session as moderator in a browser.
|
||||||
When I press "Information" in the app
|
When I open a browser tab with url "$WWWROOT"
|
||||||
And I press "Open in browser" in the app
|
And I am on the "bbb1" Activity page logged in as teacher1
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I click on "Join session" "link"
|
And I click on "Join session" "link"
|
||||||
And I wait for the BigBlueButton room to start
|
And I wait for the BigBlueButton room to start
|
||||||
And I switch back to the app
|
And I switch back to the app
|
||||||
And I press "Close" in the app
|
|
||||||
And I pull to refresh until I find "The session is in progress" in the app
|
And I pull to refresh until I find "The session is in progress" in the app
|
||||||
Then I should find "1" near "Moderator" in the app
|
Then I should find "1" near "Moderator" in the app
|
||||||
And I should find "0" near "Viewer" in the app
|
And I should find "0" near "Viewer" in the app
|
||||||
|
|
|
@ -31,10 +31,8 @@ Feature: Test basic usage of choice activity in app
|
||||||
Given I entered the choice activity "Choice name" on course "Course 1" as "teacher1" in the app
|
Given I entered the choice activity "Choice name" on course "Course 1" as "teacher1" in the app
|
||||||
Then I should find "Test choice description" in the app
|
Then I should find "Test choice description" in the app
|
||||||
|
|
||||||
When I press "Information" in the app
|
When I open a browser tab with url "$WWWROOT"
|
||||||
And I press "Open in browser" in the app
|
And I am on the "choice1" Activity page logged in as teacher1
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I press "Actions menu"
|
And I press "Actions menu"
|
||||||
And I follow "View 1 responses"
|
And I follow "View 1 responses"
|
||||||
And I press "Download in text format"
|
And I press "Download in text format"
|
||||||
|
|
|
@ -176,10 +176,8 @@ Feature: Test basic usage of choice activity in app
|
||||||
Given I entered the choice activity "Choice name" on course "Course 1" as "teacher1" in the app
|
Given I entered the choice activity "Choice name" on course "Course 1" as "teacher1" in the app
|
||||||
Then I should find "Test choice description" in the app
|
Then I should find "Test choice description" in the app
|
||||||
|
|
||||||
When I press "Information" in the app
|
When I open a browser tab with url "$WWWROOT"
|
||||||
And I press "Open in browser" in the app
|
And I am on the "choice1" Activity page logged in as teacher1
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I follow "Responses"
|
And I follow "Responses"
|
||||||
And I press "Download in text format"
|
And I press "Download in text format"
|
||||||
# TODO Then I should find "..." in the downloads folder
|
# TODO Then I should find "..." in the downloads folder
|
||||||
|
|
|
@ -138,7 +138,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
subfolder: folder,
|
subfolder: folder,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hash = <string> Md5.hashAsciiStr(folder.filepath);
|
const hash = Md5.hashAsciiStr(folder.filepath);
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
`${AddonModFolderModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/${hash}`,
|
`${AddonModFolderModuleHandlerService.PAGE_NAME}/${this.courseId}/${this.module.id}/${hash}`,
|
||||||
|
|
|
@ -207,12 +207,9 @@ Feature: Attempt a quiz in app
|
||||||
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
||||||
Then the UI should match the snapshot
|
Then the UI should match the snapshot
|
||||||
|
|
||||||
Given I entered the quiz activity "Quiz 1" on course "Course 1" as "teacher1" in the app
|
Given I open a browser tab with url "$WWWROOT"
|
||||||
When I press "Information" in the app
|
And I am on the "quiz1" Activity page logged in as teacher1
|
||||||
And I press "Open in browser" in the app
|
When I follow "Attempts: 1"
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I follow "Attempts: 1"
|
|
||||||
And I follow "Review attempt"
|
And I follow "Review attempt"
|
||||||
Then I should see "Finished"
|
Then I should see "Finished"
|
||||||
And I should see "1.00/2.00"
|
And I should see "1.00/2.00"
|
||||||
|
|
|
@ -206,11 +206,8 @@ Feature: Attempt a quiz in app
|
||||||
When I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(1) p:nth-child(2)" with "[Started on date]"
|
When I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(1) p:nth-child(2)" with "[Started on date]"
|
||||||
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
||||||
|
|
||||||
Given I entered the quiz activity "Quiz 1" on course "Course 1" as "teacher1" in the app
|
Given I open a browser tab with url "$WWWROOT"
|
||||||
When I press "Information" in the app
|
When I am on the "quiz1" Activity page logged in as teacher1
|
||||||
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 follow "Attempts: 1"
|
And I follow "Attempts: 1"
|
||||||
And I follow "Review attempt"
|
And I follow "Review attempt"
|
||||||
Then I should see "Finished"
|
Then I should see "Finished"
|
||||||
|
|
|
@ -211,12 +211,9 @@ Feature: Attempt a quiz in app
|
||||||
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
And I replace "/.*/" within "page-addon-mod-quiz-review core-loading > ion-card ion-item:nth-child(3) p:nth-child(2)" with "[Completed on date]"
|
||||||
Then the UI should match the snapshot
|
Then the UI should match the snapshot
|
||||||
|
|
||||||
Given I entered the quiz activity "Quiz 1" on course "Course 1" as "teacher1" in the app
|
Given I open a browser tab with url "$WWWROOT"
|
||||||
When I press "Information" in the app
|
And I am on the "quiz1" Activity page logged in as teacher1
|
||||||
And I press "Open in browser" in the app
|
When I follow "Attempts: 1"
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I follow "Attempts: 1"
|
|
||||||
And I follow "Review attempt"
|
And I follow "Review attempt"
|
||||||
Then I should see "Finished"
|
Then I should see "Finished"
|
||||||
And I should see "1.00/2.00"
|
And I should see "1.00/2.00"
|
||||||
|
|
|
@ -233,8 +233,7 @@ Feature: Test basic usage of SCORM activity in app
|
||||||
Then I should find "2 / 11" in the app
|
Then I should find "2 / 11" in the app
|
||||||
|
|
||||||
When I open a browser tab with url "$WWWROOT"
|
When I open a browser tab with url "$WWWROOT"
|
||||||
And I log in as "admin"
|
And I am on the "System logs report" page logged in as "admin"
|
||||||
And I am on the "System logs report" page
|
|
||||||
And I set the field "id" to "Course 1"
|
And I set the field "id" to "Course 1"
|
||||||
And I set the field "user" to "Student student"
|
And I set the field "user" to "Student student"
|
||||||
And I press "Get these logs"
|
And I press "Get these logs"
|
||||||
|
|
|
@ -638,7 +638,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
* @returns Promise.
|
* @returns Promise.
|
||||||
*/
|
*/
|
||||||
protected async openPageOrSubwiki(options: AddonModWikiOpenPageOptions): Promise<void> {
|
protected async openPageOrSubwiki(options: AddonModWikiOpenPageOptions): Promise<void> {
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify({
|
const hash = Md5.hashAsciiStr(JSON.stringify({
|
||||||
...options,
|
...options,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class AddonModWikiPageOrMapLinkHandlerService extends CoreContentLinksHan
|
||||||
{ siteId, readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
|
{ siteId, readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
|
||||||
);
|
);
|
||||||
|
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify({
|
const hash = Md5.hashAsciiStr(JSON.stringify({
|
||||||
pageId: page.id,
|
pageId: page.id,
|
||||||
pageTitle: page.title,
|
pageTitle: page.title,
|
||||||
subwikiId: page.subwikiid,
|
subwikiId: page.subwikiid,
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseAccess,
|
CoreCourseAccess,
|
||||||
CoreCourseOptionsHandler,
|
CoreCourseOptionsHandler,
|
||||||
|
@ -47,11 +47,11 @@ export class AddonNotesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (accessData && accessData.type === CoreCourseProvider.ACCESS_GUEST) {
|
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||||
return false; // Not enabled for guest access.
|
return false; // Not enabled for guest access.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.notes !== undefined) {
|
if (navOptions?.notes !== undefined) {
|
||||||
return navOptions.notes;
|
return navOptions.notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,7 +267,7 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy {
|
||||||
params.filename = '';
|
params.filename = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(params));
|
const hash = Md5.hashAsciiStr(JSON.stringify(params));
|
||||||
|
|
||||||
CoreNavigator.navigate(`../${hash}`, { params });
|
CoreNavigator.navigate(`../${hash}`, { params });
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,32 +12,40 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Type } from '@angular/core';
|
/**
|
||||||
|
* Get core errors exported objects.
|
||||||
|
*
|
||||||
|
* @returns Core errors exported objects.
|
||||||
|
*/
|
||||||
|
export async function getCoreErrorsExportedObjects(): Promise<Record<string, unknown>> {
|
||||||
|
|
||||||
import { CoreError } from './error';
|
const { CoreError } = await import('./error');
|
||||||
import { CoreWSError } from './wserror';
|
const { CoreWSError } = await import('./wserror');
|
||||||
import { CoreCanceledError } from './cancelederror';
|
const { CoreCanceledError } = await import('./cancelederror');
|
||||||
import { CoreSilentError } from './silenterror';
|
const { CoreSilentError } = await import('./silenterror');
|
||||||
import { CoreAjaxError } from './ajaxerror';
|
const { CoreAjaxError } = await import('./ajaxerror');
|
||||||
import { CoreAjaxWSError } from './ajaxwserror';
|
const { CoreAjaxWSError } = await import('./ajaxwserror');
|
||||||
import { CoreCaptureError } from './captureerror';
|
const { CoreCaptureError } = await import('./captureerror');
|
||||||
import { CoreNetworkError } from './network-error';
|
const { CoreNetworkError } = await import('./network-error');
|
||||||
import { CoreSiteError } from './siteerror';
|
const { CoreSiteError } = await import('./siteerror');
|
||||||
import { CoreLoginError } from './loginerror';
|
const { CoreLoginError } = await import('./loginerror');
|
||||||
import { CoreErrorWithOptions } from './errorwithoptions';
|
const { CoreErrorWithOptions } = await import('./errorwithoptions');
|
||||||
import { CoreHttpError } from './httperror';
|
const { CoreHttpError } = await import('./httperror');
|
||||||
|
|
||||||
export const CORE_ERRORS_CLASSES: Type<unknown>[] = [
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
CoreAjaxError,
|
return {
|
||||||
CoreAjaxWSError,
|
CoreError,
|
||||||
CoreCanceledError,
|
CoreWSError,
|
||||||
CoreCaptureError,
|
CoreCanceledError,
|
||||||
CoreError,
|
CoreSilentError,
|
||||||
CoreNetworkError,
|
CoreAjaxError,
|
||||||
CoreSilentError,
|
CoreAjaxWSError,
|
||||||
CoreSiteError,
|
CoreCaptureError,
|
||||||
CoreLoginError,
|
CoreNetworkError,
|
||||||
CoreWSError,
|
CoreSiteError,
|
||||||
CoreErrorWithOptions,
|
CoreLoginError,
|
||||||
CoreHttpError,
|
CoreErrorWithOptions,
|
||||||
];
|
CoreHttpError,
|
||||||
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
}
|
||||||
|
|
|
@ -1010,7 +1010,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
protected getCacheId(method: string, data: any): string {
|
protected getCacheId(method: string, data: any): string {
|
||||||
return <string> Md5.hashAsciiStr(method + ':' + CoreUtils.sortAndStringify(data));
|
return Md5.hashAsciiStr(method + ':' + CoreUtils.sortAndStringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -171,7 +171,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
protected async getComponentClass(): Promise<Type<unknown>> {
|
protected async getComponentClass(): Promise<Type<unknown>> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const compileInstance = this;
|
const compileInstance = this;
|
||||||
const lazyLibraries = await CoreCompile.getLazyLibraries();
|
await CoreCompile.loadLibraries();
|
||||||
|
|
||||||
// Create the component, using the text as the template.
|
// Create the component, using the text as the template.
|
||||||
return class CoreCompileHtmlFakeComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
|
return class CoreCompileHtmlFakeComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
|
||||||
|
@ -187,10 +187,10 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
this['dataArray'] = [];
|
this['dataArray'] = [];
|
||||||
|
|
||||||
// Inject the libraries.
|
// Inject the libraries.
|
||||||
CoreCompile.injectLibraries(this, [
|
CoreCompile.injectLibraries(
|
||||||
...lazyLibraries,
|
this,
|
||||||
...compileInstance.extraProviders,
|
compileInstance.extraProviders,
|
||||||
]);
|
);
|
||||||
|
|
||||||
// Always add these elements, they could be needed on component init (componentObservable).
|
// Always add these elements, they could be needed on component init (componentObservable).
|
||||||
this['ChangeDetectorRef'] = compileInstance.changeDetector;
|
this['ChangeDetectorRef'] = compileInstance.changeDetector;
|
||||||
|
|
|
@ -43,8 +43,8 @@ import { makeSingleton } from '@singletons';
|
||||||
import { getCoreServices } from '@/core/core.module';
|
import { getCoreServices } from '@/core/core.module';
|
||||||
import { getBlockServices } from '@features/block/block.module';
|
import { getBlockServices } from '@features/block/block.module';
|
||||||
import { getCommentsServices } from '@features/comments/comments.module';
|
import { getCommentsServices } from '@features/comments/comments.module';
|
||||||
import { getContentLinksServices } from '@features/contentlinks/contentlinks.module';
|
import { getContentLinksExportedObjects, getContentLinksServices } from '@features/contentlinks/contentlinks.module';
|
||||||
import { getCourseServices } from '@features/course/course.module';
|
import { getCourseExportedObjects, getCourseServices } from '@features/course/course.module';
|
||||||
import { getCoursesServices } from '@features/courses/courses.module';
|
import { getCoursesServices } from '@features/courses/courses.module';
|
||||||
import { getEditorServices } from '@features/editor/editor.module';
|
import { getEditorServices } from '@features/editor/editor.module';
|
||||||
import { getEnrolServices } from '@features/enrol/enrol.module';
|
import { getEnrolServices } from '@features/enrol/enrol.module';
|
||||||
|
@ -88,13 +88,8 @@ import { CoreUrl } from '@singletons/url';
|
||||||
import { CoreWindow } from '@singletons/window';
|
import { CoreWindow } from '@singletons/window';
|
||||||
import { CoreCache } from '@classes/cache';
|
import { CoreCache } from '@classes/cache';
|
||||||
import { CoreDelegate } from '@classes/delegate';
|
import { CoreDelegate } from '@classes/delegate';
|
||||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
|
||||||
import { CoreContentLinksModuleGradeHandler } from '@features/contentlinks/classes/module-grade-handler';
|
|
||||||
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
|
||||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
|
||||||
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
|
|
||||||
import { CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
|
import { CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
|
||||||
import { CORE_ERRORS_CLASSES } from '@classes/errors/errors';
|
import { getCoreErrorsExportedObjects } from '@classes/errors/errors';
|
||||||
import { CoreNetwork } from '@services/network';
|
import { CoreNetwork } from '@services/network';
|
||||||
|
|
||||||
// Import all core modules that define components, directives and pipes.
|
// Import all core modules that define components, directives and pipes.
|
||||||
|
@ -109,19 +104,6 @@ import { CoreBlockComponentsModule } from '@features/block/components/components
|
||||||
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
|
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
|
||||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
|
|
||||||
// Import some components so they can be injected dynamically.
|
|
||||||
import { CoreCourseUnsupportedModuleComponent } from '@features/course/components/unsupported-module/unsupported-module';
|
|
||||||
import { CoreCourseFormatSingleActivityComponent } from '@features/course/format/singleactivity/components/singleactivity';
|
|
||||||
import { CoreSitePluginsModuleIndexComponent } from '@features/siteplugins/components/module-index/module-index';
|
|
||||||
import { CoreSitePluginsBlockComponent } from '@features/siteplugins/components/block/block';
|
|
||||||
import { CoreSitePluginsCourseFormatComponent } from '@features/siteplugins/components/course-format/course-format';
|
|
||||||
import { CoreSitePluginsQuestionComponent } from '@features/siteplugins/components/question/question';
|
|
||||||
import { CoreSitePluginsQuestionBehaviourComponent } from '@features/siteplugins/components/question-behaviour/question-behaviour';
|
|
||||||
import { CoreSitePluginsUserProfileFieldComponent } from '@features/siteplugins/components/user-profile-field/user-profile-field';
|
|
||||||
import { CoreSitePluginsQuizAccessRuleComponent } from '@features/siteplugins/components/quiz-access-rule/quiz-access-rule';
|
|
||||||
import { CoreSitePluginsAssignFeedbackComponent } from '@features/siteplugins/components/assign-feedback/assign-feedback';
|
|
||||||
import { CoreSitePluginsAssignSubmissionComponent } from '@features/siteplugins/components/assign-submission/assign-submission';
|
|
||||||
|
|
||||||
// Import addon providers. Do not import database module because it causes circular dependencies.
|
// Import addon providers. Do not import database module because it causes circular dependencies.
|
||||||
import { getBadgesServices } from '@addons/badges/badges.module';
|
import { getBadgesServices } from '@addons/badges/badges.module';
|
||||||
import { getCalendarServices } from '@addons/calendar/calendar.module';
|
import { getCalendarServices } from '@addons/calendar/calendar.module';
|
||||||
|
@ -142,6 +124,8 @@ import { CorePlatform } from '@services/platform';
|
||||||
|
|
||||||
import { CoreAutoLogoutService } from '@features/autologout/services/autologout';
|
import { CoreAutoLogoutService } from '@features/autologout/services/autologout';
|
||||||
import { CoreSitePluginsProvider } from '@features/siteplugins/services/siteplugins';
|
import { CoreSitePluginsProvider } from '@features/siteplugins/services/siteplugins';
|
||||||
|
import { getSitePluginsExportedObjects } from '@features/siteplugins/siteplugins.module';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
||||||
|
@ -177,6 +161,9 @@ export class CoreCompileProvider {
|
||||||
getModWorkshopComponentModules,
|
getModWorkshopComponentModules,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected libraries?: unknown[];
|
||||||
|
protected exportedObjects?: Record<string, unknown>;
|
||||||
|
|
||||||
constructor(protected injector: Injector) {
|
constructor(protected injector: Injector) {
|
||||||
this.logger = CoreLogger.getInstance('CoreCompileProvider');
|
this.logger = CoreLogger.getInstance('CoreCompileProvider');
|
||||||
}
|
}
|
||||||
|
@ -264,26 +251,28 @@ export class CoreCompileProvider {
|
||||||
* Inject all the core libraries in a certain object.
|
* Inject all the core libraries in a certain object.
|
||||||
*
|
*
|
||||||
* @param instance The instance where to inject the libraries.
|
* @param instance The instance where to inject the libraries.
|
||||||
* @param extraProviders Extra imported providers if needed and not imported by this class.
|
* @param extraLibraries Extra imported providers if needed and not imported by this class.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
injectLibraries(instance: any, extraProviders: Type<unknown>[] = []): void {
|
injectLibraries(instance: any, extraLibraries: Type<unknown>[] = []): void {
|
||||||
const providers = [
|
if (!this.libraries || !this.exportedObjects) {
|
||||||
...extraProviders,
|
throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.');
|
||||||
CoreAutoLogoutService,
|
}
|
||||||
CoreSitePluginsProvider,
|
|
||||||
...this.OTHER_SERVICES,
|
const libraries = [
|
||||||
|
...this.libraries,
|
||||||
|
...extraLibraries,
|
||||||
];
|
];
|
||||||
|
|
||||||
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
||||||
for (const i in providers) {
|
for (const i in libraries) {
|
||||||
const providerDef = providers[i];
|
const libraryDef = libraries[i];
|
||||||
if (typeof providerDef === 'function' && providerDef.name) {
|
if (typeof libraryDef === 'function' && libraryDef.name) {
|
||||||
try {
|
try {
|
||||||
// Inject the provider to the instance. We use the class name as the property name.
|
// Inject the provider to the instance. We use the class name as the property name.
|
||||||
instance[providerDef.name.replace(/DelegateService$/, 'Delegate')] = this.injector.get<Provider>(providerDef);
|
instance[libraryDef.name.replace(/DelegateService$/, 'Delegate')] = this.injector.get<Provider>(libraryDef);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.logger.error('Error injecting provider', providerDef.name, ex);
|
this.logger.error('Error injecting provider', libraryDef.name, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,27 +308,26 @@ export class CoreCompileProvider {
|
||||||
instance['CoreCache'] = CoreCache; // @deprecated since 4.4, plugins should use plain objects instead.
|
instance['CoreCache'] = CoreCache; // @deprecated since 4.4, plugins should use plain objects instead.
|
||||||
instance['CoreDelegate'] = CoreDelegate;
|
instance['CoreDelegate'] = CoreDelegate;
|
||||||
instance['CorePromisedValue'] = CorePromisedValue;
|
instance['CorePromisedValue'] = CorePromisedValue;
|
||||||
instance['CoreContentLinksHandlerBase'] = CoreContentLinksHandlerBase;
|
|
||||||
instance['CoreContentLinksModuleGradeHandler'] = CoreContentLinksModuleGradeHandler;
|
|
||||||
instance['CoreContentLinksModuleIndexHandler'] = CoreContentLinksModuleIndexHandler;
|
|
||||||
instance['CoreCourseActivityPrefetchHandlerBase'] = CoreCourseActivityPrefetchHandlerBase;
|
|
||||||
instance['CoreCourseResourcePrefetchHandlerBase'] = CoreCourseResourcePrefetchHandlerBase;
|
|
||||||
instance['CoreCourseUnsupportedModuleComponent'] = CoreCourseUnsupportedModuleComponent;
|
|
||||||
instance['CoreCourseFormatSingleActivityComponent'] = CoreCourseFormatSingleActivityComponent;
|
|
||||||
instance['CoreSitePluginsModuleIndexComponent'] = CoreSitePluginsModuleIndexComponent;
|
|
||||||
instance['CoreSitePluginsBlockComponent'] = CoreSitePluginsBlockComponent;
|
|
||||||
instance['CoreSitePluginsCourseFormatComponent'] = CoreSitePluginsCourseFormatComponent;
|
|
||||||
instance['CoreSitePluginsQuestionComponent'] = CoreSitePluginsQuestionComponent;
|
|
||||||
instance['CoreSitePluginsQuestionBehaviourComponent'] = CoreSitePluginsQuestionBehaviourComponent;
|
|
||||||
instance['CoreSitePluginsUserProfileFieldComponent'] = CoreSitePluginsUserProfileFieldComponent;
|
|
||||||
instance['CoreSitePluginsQuizAccessRuleComponent'] = CoreSitePluginsQuizAccessRuleComponent;
|
|
||||||
instance['CoreSitePluginsAssignFeedbackComponent'] = CoreSitePluginsAssignFeedbackComponent;
|
|
||||||
instance['CoreSitePluginsAssignSubmissionComponent'] = CoreSitePluginsAssignSubmissionComponent;
|
|
||||||
instance['CoreGeolocationError'] = CoreGeolocationError;
|
instance['CoreGeolocationError'] = CoreGeolocationError;
|
||||||
instance['CoreGeolocationErrorReason'] = CoreGeolocationErrorReason;
|
instance['CoreGeolocationErrorReason'] = CoreGeolocationErrorReason;
|
||||||
CORE_ERRORS_CLASSES.forEach((classDef) => {
|
|
||||||
instance[classDef.name] = classDef;
|
// Inject exported objects.
|
||||||
});
|
for (const name in this.exportedObjects) {
|
||||||
|
instance[name] = this.exportedObjects[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all the libraries needed for the compile service.
|
||||||
|
*/
|
||||||
|
async loadLibraries(): Promise<void> {
|
||||||
|
if (!this.libraries) {
|
||||||
|
this.libraries = await this.getLibraries();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.exportedObjects) {
|
||||||
|
this.exportedObjects = await this.getExportedObjects();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -347,7 +335,7 @@ export class CoreCompileProvider {
|
||||||
*
|
*
|
||||||
* @returns Lazy libraries.
|
* @returns Lazy libraries.
|
||||||
*/
|
*/
|
||||||
async getLazyLibraries(): Promise<Type<unknown>[]> {
|
protected async getLibraries(): Promise<unknown[]> {
|
||||||
const services = await Promise.all([
|
const services = await Promise.all([
|
||||||
getCoreServices(),
|
getCoreServices(),
|
||||||
getBlockServices(),
|
getBlockServices(),
|
||||||
|
@ -389,7 +377,30 @@ export class CoreCompileProvider {
|
||||||
getPrivateFilesServices(),
|
getPrivateFilesServices(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return services.flat();
|
const lazyLibraries = services.flat();
|
||||||
|
|
||||||
|
return [
|
||||||
|
...lazyLibraries,
|
||||||
|
CoreAutoLogoutService,
|
||||||
|
CoreSitePluginsProvider,
|
||||||
|
...this.OTHER_SERVICES,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lazy exported objects to inject.
|
||||||
|
*
|
||||||
|
* @returns Lazy exported objects.
|
||||||
|
*/
|
||||||
|
protected async getExportedObjects(): Promise<Record<string, unknown>> {
|
||||||
|
const objects = await Promise.all([
|
||||||
|
getCoreErrorsExportedObjects(),
|
||||||
|
getCourseExportedObjects(),
|
||||||
|
getContentLinksExportedObjects(),
|
||||||
|
getSitePluginsExportedObjects(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Object.assign({}, ...objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,25 @@ export async function getContentLinksServices(): Promise<Type<unknown>[]> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content links exported objects.
|
||||||
|
*
|
||||||
|
* @returns Content links exported objects.
|
||||||
|
*/
|
||||||
|
export async function getContentLinksExportedObjects(): Promise<Record<string, unknown>> {
|
||||||
|
const { CoreContentLinksHandlerBase } = await import ('@features/contentlinks/classes/base-handler');
|
||||||
|
const { CoreContentLinksModuleGradeHandler } = await import ('@features/contentlinks/classes/module-grade-handler');
|
||||||
|
const { CoreContentLinksModuleIndexHandler } = await import ('@features/contentlinks/classes/module-index-handler');
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
return {
|
||||||
|
CoreContentLinksHandlerBase,
|
||||||
|
CoreContentLinksModuleGradeHandler,
|
||||||
|
CoreContentLinksModuleIndexHandler,
|
||||||
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CoreContentLinksComponentsModule,
|
CoreContentLinksComponentsModule,
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
</h1>
|
</h1>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button fill="clear" *ngIf="displayOptions.displayOpenInBrowser && externalUrl" [href]="externalUrl" core-link
|
<ion-button fill="clear" *ngIf="displayOptions.displayOpenInBrowser && externalUrl" [href]="externalUrl" core-link
|
||||||
[showBrowserWarning]="false" [attr.aria-label]="'core.openinbrowser' | translate" slot="end">
|
[showBrowserWarning]="false" [attr.aria-label]="'core.openinbrowser' | translate" slot="end" [class.hidden]="!isTeacher"
|
||||||
|
class="core-module-oib-button">
|
||||||
<ion-icon name="fas-up-right-from-square" slot="icon-only" aria-hidden="true" />
|
<ion-icon name="fas-up-right-from-square" slot="icon-only" aria-hidden="true" />
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -39,3 +39,7 @@ ion-item.card-header {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-module-oib-button.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
||||||
course?: CoreEnrolledCourseData;
|
course?: CoreEnrolledCourseData;
|
||||||
modicon = '';
|
modicon = '';
|
||||||
moduleNameTranslated = '';
|
moduleNameTranslated = '';
|
||||||
|
isTeacher = false;
|
||||||
|
|
||||||
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
||||||
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
|
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
|
||||||
|
@ -269,13 +270,14 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
||||||
* Fetch course.
|
* Fetch course.
|
||||||
*/
|
*/
|
||||||
protected async fetchCourse(): Promise<void> {
|
protected async fetchCourse(): Promise<void> {
|
||||||
// Fix that.
|
|
||||||
try {
|
try {
|
||||||
this.course = await CoreCourses.getUserCourse(this.courseId, true);
|
this.course = await CoreCourses.getUserCourse(this.courseId, true);
|
||||||
} catch {
|
} catch {
|
||||||
// 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.
|
||||||
this.course = await CoreCourses.getCourse(this.courseId);
|
this.course = await CoreCourses.getCourse(this.courseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isTeacher = await CoreUtils.ignoreErrors(CoreCourseHelper.guessIsTeacher(this.courseId, this.course), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -62,6 +62,31 @@ export async function getCourseServices(): Promise<Type<unknown>[]> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get course exported objects.
|
||||||
|
*
|
||||||
|
* @returns Course exported objects.
|
||||||
|
*/
|
||||||
|
export async function getCourseExportedObjects(): Promise<Record<string, unknown>> {
|
||||||
|
const { CoreCourseActivityPrefetchHandlerBase } = await import('@features/course/classes/activity-prefetch-handler');
|
||||||
|
const { CoreCourseResourcePrefetchHandlerBase } = await import('@features/course/classes/resource-prefetch-handler');
|
||||||
|
const { CoreCourseAccessDataType } = await import('@features/course/services/course');
|
||||||
|
const { CoreCourseUnsupportedModuleComponent } =
|
||||||
|
await import ('@features/course/components/unsupported-module/unsupported-module');
|
||||||
|
const { CoreCourseFormatSingleActivityComponent } =
|
||||||
|
await import ('@features/course/format/singleactivity/components/singleactivity');
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
return {
|
||||||
|
CoreCourseActivityPrefetchHandlerBase,
|
||||||
|
CoreCourseResourcePrefetchHandlerBase,
|
||||||
|
CoreCourseUnsupportedModuleComponent,
|
||||||
|
CoreCourseFormatSingleActivityComponent,
|
||||||
|
CoreCourseAccessDataType,
|
||||||
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
}
|
||||||
|
|
||||||
export const COURSE_PAGE_NAME = 'course';
|
export const COURSE_PAGE_NAME = 'course';
|
||||||
export const CONTENTS_PAGE_NAME = 'contents';
|
export const CONTENTS_PAGE_NAME = 'contents';
|
||||||
export const COURSE_CONTENTS_PATH = `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${CONTENTS_PAGE_NAME}`;
|
export const COURSE_CONTENTS_PATH = `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${CONTENTS_PAGE_NAME}`;
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button *ngIf="displayOpenInBrowser" fill="clear" [href]="courseUrl" core-link [showBrowserWarning]="false"
|
<ion-button *ngIf="displayOpenInBrowser" fill="clear" [href]="courseUrl" core-link [showBrowserWarning]="false"
|
||||||
[attr.aria-label]="'core.openinbrowser' | translate" slot="end">
|
[attr.aria-label]="'core.openinbrowser' | translate" slot="end" [class.hidden]="!isTeacher"
|
||||||
|
class="core-course-oib-button">
|
||||||
<ion-icon name="fas-up-right-from-square" slot="icon-only" aria-hidden="true" />
|
<ion-icon name="fas-up-right-from-square" slot="icon-only" aria-hidden="true" />
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -73,6 +73,7 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
progress?: number;
|
progress?: number;
|
||||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
displayOpenInBrowser = false;
|
displayOpenInBrowser = false;
|
||||||
|
isTeacher = false;
|
||||||
|
|
||||||
protected actionSheet?: HTMLIonActionSheetElement;
|
protected actionSheet?: HTMLIonActionSheetElement;
|
||||||
protected waitStart = 0;
|
protected waitStart = 0;
|
||||||
|
@ -172,6 +173,9 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await this.loadMenuHandlers(refresh);
|
await this.loadMenuHandlers(refresh);
|
||||||
|
|
||||||
|
// After loading menu handlers, admOptions should be available.
|
||||||
|
this.isTeacher = await CoreUtils.ignoreErrors(CoreCourseHelper.guessIsTeacher(this.courseId, this.course), false);
|
||||||
|
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +258,7 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async loadMenuHandlers(refresh?: boolean): Promise<void> {
|
protected async loadMenuHandlers(refresh?: boolean): Promise<void> {
|
||||||
if (!this.course) {
|
if (!this.course || !this.canAccessCourse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,4 +104,8 @@
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-course-oib-button.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2058,6 +2058,30 @@ export class CoreCourseHelperProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess if the user is a teacher in a course.
|
||||||
|
*
|
||||||
|
* @param courseId Course Id.
|
||||||
|
* @param course Course object.
|
||||||
|
* @returns Promise resolved with boolean: whether the user is a teacher.
|
||||||
|
*/
|
||||||
|
async guessIsTeacher(
|
||||||
|
courseId: number,
|
||||||
|
course?: CoreEnrolledCourseData | CoreCourseSearchedData,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (course && 'admOptions' in course && course.admOptions) {
|
||||||
|
return !!course.admOptions['reports'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not loaded yet, try to load it.
|
||||||
|
const adminOptions = await CoreCourses.getUserAdministrationOptions(
|
||||||
|
[courseId],
|
||||||
|
{ readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
|
||||||
|
);
|
||||||
|
|
||||||
|
return !!adminOptions[courseId]?.['reports'];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreCourseHelper = makeSingleton(CoreCourseHelperProvider);
|
export const CoreCourseHelper = makeSingleton(CoreCourseHelperProvider);
|
||||||
|
|
|
@ -24,10 +24,9 @@ import {
|
||||||
CoreCoursesProvider,
|
CoreCoursesProvider,
|
||||||
CoreCourseUserAdminOrNavOptionIndexed,
|
CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
} from '@features/courses/services/courses';
|
} from '@features/courses/services/courses';
|
||||||
import { CoreCourseProvider } from './course';
|
import { CoreCourseAccessDataType } from './course';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -313,20 +312,20 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||||
* @returns Promise resolved with array of handlers.
|
* @returns Promise resolved with array of handlers.
|
||||||
*/
|
*/
|
||||||
protected async getHandlersForAccess(
|
protected async updateHandlersForAccess(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
refresh: boolean,
|
refresh: boolean,
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<CoreCourseOptionsHandler[]> {
|
): Promise<void> {
|
||||||
|
|
||||||
// If the handlers aren't loaded, do not refresh.
|
// If the handlers aren't loaded, do not refresh.
|
||||||
if (!this.loaded[courseId]) {
|
if (!this.loaded[courseId]) {
|
||||||
refresh = false;
|
refresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type != accessData.type) {
|
if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type !== accessData.type) {
|
||||||
if (!this.coursesHandlers[courseId]) {
|
if (!this.coursesHandlers[courseId]) {
|
||||||
this.coursesHandlers[courseId] = {
|
this.coursesHandlers[courseId] = {
|
||||||
access: accessData,
|
access: accessData,
|
||||||
|
@ -347,8 +346,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.coursesHandlers[courseId].deferred;
|
await this.coursesHandlers[courseId].deferred;
|
||||||
|
|
||||||
return this.coursesHandlers[courseId].enabledHandlers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -358,18 +355,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param course The course object.
|
* @param course The course object.
|
||||||
* @param refresh True if it should refresh the list.
|
* @param refresh True if it should refresh the list.
|
||||||
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
|
||||||
* @returns Promise resolved with array of handlers.
|
* @returns Promise resolved with array of handlers.
|
||||||
*/
|
*/
|
||||||
getHandlersToDisplay(
|
getHandlersToDisplay(
|
||||||
course: CoreCourseAnyCourseData,
|
course: CoreCourseAnyCourseData,
|
||||||
refresh = false,
|
refresh = false,
|
||||||
isGuest = false,
|
isGuest = false,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
): Promise<CoreCourseOptionsHandlerToDisplay[]> {
|
): Promise<CoreCourseOptionsHandlerToDisplay[]> {
|
||||||
return this.getHandlersToDisplayInternal(false, course, refresh, isGuest, navOptions, admOptions) as
|
return this.getHandlersToDisplayInternal(false, course, refresh, isGuest) as
|
||||||
Promise<CoreCourseOptionsHandlerToDisplay[]>;
|
Promise<CoreCourseOptionsHandlerToDisplay[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,18 +373,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param course The course object.
|
* @param course The course object.
|
||||||
* @param refresh True if it should refresh the list.
|
* @param refresh True if it should refresh the list.
|
||||||
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
|
||||||
* @returns Promise resolved with array of handlers.
|
* @returns Promise resolved with array of handlers.
|
||||||
*/
|
*/
|
||||||
getMenuHandlersToDisplay(
|
getMenuHandlersToDisplay(
|
||||||
course: CoreCourseAnyCourseData,
|
course: CoreCourseAnyCourseData,
|
||||||
refresh = false,
|
refresh = false,
|
||||||
isGuest = false,
|
isGuest = false,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
): Promise<CoreCourseOptionsMenuHandlerToDisplay[]> {
|
): Promise<CoreCourseOptionsMenuHandlerToDisplay[]> {
|
||||||
return this.getHandlersToDisplayInternal(true, course, refresh, isGuest, navOptions, admOptions) as
|
return this.getHandlersToDisplayInternal(true, course, refresh, isGuest) as
|
||||||
Promise<CoreCourseOptionsMenuHandlerToDisplay[]>;
|
Promise<CoreCourseOptionsMenuHandlerToDisplay[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,8 +392,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param course The course object.
|
* @param course The course object.
|
||||||
* @param refresh True if it should refresh the list.
|
* @param refresh True if it should refresh the list.
|
||||||
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
* @param isGuest Whether user is using an ACCESS_GUEST enrolment method.
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
|
||||||
* @returns Promise resolved with array of handlers.
|
* @returns Promise resolved with array of handlers.
|
||||||
*/
|
*/
|
||||||
protected async getHandlersToDisplayInternal(
|
protected async getHandlersToDisplayInternal(
|
||||||
|
@ -412,36 +399,30 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
course: CoreCourseAnyCourseData,
|
course: CoreCourseAnyCourseData,
|
||||||
refresh = false,
|
refresh = false,
|
||||||
isGuest = false,
|
isGuest = false,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
|
||||||
): Promise<CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[]> {
|
): Promise<CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[]> {
|
||||||
|
|
||||||
const courseWithOptions: CoreCourseAnyCourseDataWithOptions = course;
|
const courseWithOptions: CoreCourseAnyCourseDataWithOptions = course;
|
||||||
const accessData = {
|
const accessData = {
|
||||||
type: isGuest ? CoreCourseProvider.ACCESS_GUEST : CoreCourseProvider.ACCESS_DEFAULT,
|
type: isGuest ? CoreCourseAccessDataType.ACCESS_GUEST : CoreCourseAccessDataType.ACCESS_DEFAULT,
|
||||||
};
|
};
|
||||||
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
|
|
||||||
if (navOptions) {
|
|
||||||
courseWithOptions.navOptions = navOptions;
|
|
||||||
}
|
|
||||||
if (admOptions) {
|
|
||||||
courseWithOptions.admOptions = admOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.loadCourseOptions(courseWithOptions, refresh);
|
await this.loadCourseOptions(courseWithOptions, refresh);
|
||||||
|
|
||||||
// Call getHandlersForAccess to make sure the handlers have been loaded.
|
// Call updateHandlersForAccess to make sure the handlers have been loaded.
|
||||||
await this.getHandlersForAccess(course.id, refresh, accessData, courseWithOptions.navOptions, courseWithOptions.admOptions);
|
await this.updateHandlersForAccess(
|
||||||
|
course.id,
|
||||||
|
refresh,
|
||||||
|
accessData,
|
||||||
|
courseWithOptions.navOptions,
|
||||||
|
courseWithOptions.admOptions,
|
||||||
|
);
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
let handlerList: CoreCourseOptionsMenuHandler[] | CoreCourseOptionsHandler[];
|
const handlerList = menu
|
||||||
if (menu) {
|
? this.coursesHandlers[course.id].enabledMenuHandlers
|
||||||
handlerList = this.coursesHandlers[course.id].enabledMenuHandlers;
|
: this.coursesHandlers[course.id].enabledHandlers;
|
||||||
} else {
|
|
||||||
handlerList = this.coursesHandlers[course.id].enabledHandlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlerList.forEach((handler: CoreCourseOptionsMenuHandler | CoreCourseOptionsHandler) => {
|
handlerList.forEach((handler: CoreCourseOptionsMenuHandler | CoreCourseOptionsHandler) => {
|
||||||
const getFunction = menu
|
const getFunction = menu
|
||||||
|
@ -461,8 +442,8 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}).catch((err) => {
|
}).catch((error) => {
|
||||||
this.logger.error('Error getting data for handler', handler.name, err);
|
this.logger.error(`Error getting data for handler ${handler.name}`, error);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -477,17 +458,44 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
return handlersToDisplay;
|
return handlersToDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the handlers for a course using a certain access type.
|
||||||
|
*
|
||||||
|
* @param courseId The course ID.
|
||||||
|
* @param refresh True if it should refresh the list.
|
||||||
|
* @param accessData Access type and data. Default, guest, ...
|
||||||
|
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||||
|
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||||
|
* @returns Promise resolved with array of handlers.
|
||||||
|
* @deprecated since 4.4.
|
||||||
|
*/
|
||||||
|
protected async hasHandlersForAccess(
|
||||||
|
courseId: number,
|
||||||
|
refresh: boolean,
|
||||||
|
accessData: CoreCourseAccess,
|
||||||
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
): Promise<boolean> {
|
||||||
|
await this.updateHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions);
|
||||||
|
|
||||||
|
const handlers = this.coursesHandlers[courseId].enabledHandlers;
|
||||||
|
|
||||||
|
return !!(handlers && handlers.length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a course has any handler enabled for default access, using course object.
|
* Check if a course has any handler enabled for default access, using course object.
|
||||||
*
|
*
|
||||||
* @param course The course object.
|
* @param course The course object.
|
||||||
* @param refresh True if it should refresh the list.
|
* @param refresh True if it should refresh the list.
|
||||||
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||||
|
* @deprecated since 4.4.
|
||||||
*/
|
*/
|
||||||
async hasHandlersForCourse(course: CoreEnrolledCourseDataWithExtraInfoAndOptions, refresh = false): Promise<boolean> {
|
async hasHandlersForCourse(course: CoreCourseAnyCourseDataWithOptions, refresh = false): Promise<boolean> {
|
||||||
// Load course options if missing.
|
// Load course options if missing.
|
||||||
await this.loadCourseOptions(course, refresh);
|
await this.loadCourseOptions(course, refresh);
|
||||||
|
|
||||||
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
return this.hasHandlersForDefault(course.id, refresh, course.navOptions, course.admOptions);
|
return this.hasHandlersForDefault(course.id, refresh, course.navOptions, course.admOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,6 +507,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||||
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||||
|
* @deprecated since 4.4.
|
||||||
*/
|
*/
|
||||||
async hasHandlersForDefault(
|
async hasHandlersForDefault(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
|
@ -506,14 +515,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Default access.
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
const accessData = {
|
return await this.hasHandlersForAccess(
|
||||||
type: CoreCourseProvider.ACCESS_DEFAULT,
|
courseId,
|
||||||
};
|
refresh,
|
||||||
|
{ type: CoreCourseAccessDataType.ACCESS_DEFAULT },
|
||||||
const handlers = await this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions);
|
navOptions,
|
||||||
|
admOptions,
|
||||||
return !!(handlers && handlers.length);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -524,6 +533,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||||
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||||
|
* @deprecated since 4.4.
|
||||||
*/
|
*/
|
||||||
async hasHandlersForGuest(
|
async hasHandlersForGuest(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
|
@ -531,14 +541,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Guest access.
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
const accessData = {
|
return await this.hasHandlersForAccess(
|
||||||
type: CoreCourseProvider.ACCESS_GUEST,
|
courseId,
|
||||||
};
|
refresh,
|
||||||
|
{ type: CoreCourseAccessDataType.ACCESS_GUEST },
|
||||||
const handlers = await this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions);
|
navOptions,
|
||||||
|
admOptions,
|
||||||
return !!(handlers && handlers.length);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -547,7 +557,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param courseId Course ID.
|
* @param courseId Course ID.
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async invalidateCourseHandlers(courseId: number): Promise<void> {
|
protected async invalidateCourseHandlers(courseId: number): Promise<void> {
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
const courseData = this.coursesHandlers[courseId];
|
const courseData = this.coursesHandlers[courseId];
|
||||||
|
|
||||||
|
@ -556,7 +566,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
courseData.enabledHandlers.forEach((handler) => {
|
courseData.enabledHandlers.forEach((handler) => {
|
||||||
if (handler?.invalidateEnabledForCourse) {
|
if (handler.invalidateEnabledForCourse) {
|
||||||
promises.push(
|
promises.push(
|
||||||
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions),
|
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions),
|
||||||
);
|
);
|
||||||
|
@ -579,7 +589,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return time == this.lastUpdateHandlersForCoursesStart[courseId];
|
return time === this.lastUpdateHandlersForCoursesStart[courseId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -590,12 +600,13 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @returns Promise resolved when done.
|
* @returns Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async loadCourseOptions(course: CoreCourseAnyCourseDataWithOptions, refresh = false): Promise<void> {
|
protected async loadCourseOptions(course: CoreCourseAnyCourseDataWithOptions, refresh = false): Promise<void> {
|
||||||
if (course.navOptions === undefined || course.admOptions === undefined || refresh) {
|
if (!refresh && course.navOptions !== undefined && course.admOptions !== undefined) {
|
||||||
|
return;
|
||||||
const options = await CoreCourses.getCoursesAdminAndNavOptions([course.id]);
|
|
||||||
course.navOptions = options.navOptions[course.id];
|
|
||||||
course.admOptions = options.admOptions[course.id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = await CoreCourses.getCoursesAdminAndNavOptions([course.id]);
|
||||||
|
course.navOptions = options.navOptions[course.id];
|
||||||
|
course.admOptions = options.admOptions[course.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -618,7 +629,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||||
* @returns Resolved when updated.
|
* @returns Resolved when updated.
|
||||||
*/
|
*/
|
||||||
async updateHandlersForCourse(
|
protected async updateHandlersForCourse(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
@ -676,5 +687,5 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
||||||
export const CoreCourseOptionsDelegate = makeSingleton(CoreCourseOptionsDelegateService);
|
export const CoreCourseOptionsDelegate = makeSingleton(CoreCourseOptionsDelegateService);
|
||||||
|
|
||||||
export type CoreCourseAccess = {
|
export type CoreCourseAccess = {
|
||||||
type: string; // Either CoreCourseProvider.ACCESS_GUEST or CoreCourseProvider.ACCESS_DEFAULT.
|
type: CoreCourseAccessDataType;
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,7 +80,7 @@ declare module '@singletons/events' {
|
||||||
/**
|
/**
|
||||||
* Course Module completion status enumeration.
|
* Course Module completion status enumeration.
|
||||||
*/
|
*/
|
||||||
export enum CoreCourseModuleCompletionStatus {
|
export const enum CoreCourseModuleCompletionStatus {
|
||||||
COMPLETION_INCOMPLETE = 0,
|
COMPLETION_INCOMPLETE = 0,
|
||||||
COMPLETION_COMPLETE = 1,
|
COMPLETION_COMPLETE = 1,
|
||||||
COMPLETION_COMPLETE_PASS = 2,
|
COMPLETION_COMPLETE_PASS = 2,
|
||||||
|
@ -90,7 +90,7 @@ export enum CoreCourseModuleCompletionStatus {
|
||||||
/**
|
/**
|
||||||
* @deprecated since 4.3 Not used anymore.
|
* @deprecated since 4.3 Not used anymore.
|
||||||
*/
|
*/
|
||||||
export enum CoreCourseCompletionMode {
|
export const enum CoreCourseCompletionMode {
|
||||||
FULL = 'full',
|
FULL = 'full',
|
||||||
BASIC = 'basic',
|
BASIC = 'basic',
|
||||||
}
|
}
|
||||||
|
@ -98,12 +98,20 @@ export enum CoreCourseCompletionMode {
|
||||||
/**
|
/**
|
||||||
* Completion tracking valid values.
|
* Completion tracking valid values.
|
||||||
*/
|
*/
|
||||||
export enum CoreCourseModuleCompletionTracking {
|
export const enum CoreCourseModuleCompletionTracking {
|
||||||
COMPLETION_TRACKING_NONE = 0,
|
COMPLETION_TRACKING_NONE = 0,
|
||||||
COMPLETION_TRACKING_MANUAL = 1,
|
COMPLETION_TRACKING_MANUAL = 1,
|
||||||
COMPLETION_TRACKING_AUTOMATIC = 2,
|
COMPLETION_TRACKING_AUTOMATIC = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CoreCourseAccessDataType = {
|
||||||
|
ACCESS_GUEST: 'courses_access_guest', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
ACCESS_DEFAULT: 'courses_access_default', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export type CoreCourseAccessDataType = typeof CoreCourseAccessDataType[keyof typeof CoreCourseAccessDataType];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding a course.
|
* Service that provides some features regarding a course.
|
||||||
*/
|
*/
|
||||||
|
@ -112,10 +120,17 @@ export class CoreCourseProvider {
|
||||||
|
|
||||||
static readonly ALL_SECTIONS_ID = -2;
|
static readonly ALL_SECTIONS_ID = -2;
|
||||||
static readonly STEALTH_MODULES_SECTION_ID = -1;
|
static readonly STEALTH_MODULES_SECTION_ID = -1;
|
||||||
static readonly ACCESS_GUEST = 'courses_access_guest';
|
|
||||||
static readonly ACCESS_DEFAULT = 'courses_access_default';
|
|
||||||
static readonly ALL_COURSES_CLEARED = -1;
|
static readonly ALL_COURSES_CLEARED = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.4 Not used anymore. Use CoreCourseAccessDataType instead.
|
||||||
|
*/
|
||||||
|
static readonly ACCESS_GUEST = CoreCourseAccessDataType.ACCESS_GUEST;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.4 Not used anymore. Use CoreCourseAccessDataType instead.
|
||||||
|
*/
|
||||||
|
static readonly ACCESS_DEFAULT = CoreCourseAccessDataType.ACCESS_DEFAULT;
|
||||||
|
|
||||||
static readonly COMPONENT = 'CoreCourse';
|
static readonly COMPONENT = 'CoreCourse';
|
||||||
|
|
||||||
readonly CORE_MODULES = [
|
readonly CORE_MODULES = [
|
||||||
|
|
|
@ -214,7 +214,7 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
|
||||||
*/
|
*/
|
||||||
async getCourseUpdates(modules: CoreCourseModuleData[], courseId: number): Promise<CourseUpdates> {
|
async getCourseUpdates(modules: CoreCourseModuleData[], courseId: number): Promise<CourseUpdates> {
|
||||||
// Check if there's already a getCourseUpdates in progress.
|
// Check if there's already a getCourseUpdates in progress.
|
||||||
const id = <string> Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules));
|
const id = Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules));
|
||||||
const siteId = CoreSites.getCurrentSiteId();
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
if (this.courseUpdatesPromises[siteId] && this.courseUpdatesPromises[siteId][id] !== undefined) {
|
if (this.courseUpdatesPromises[siteId] && this.courseUpdatesPromises[siteId][id] !== undefined) {
|
||||||
|
|
|
@ -101,18 +101,9 @@ Feature: Test basic usage of one course in app
|
||||||
And I should not find "Test glossary" in the app
|
And I should not find "Test glossary" in the app
|
||||||
|
|
||||||
Scenario: Guest access
|
Scenario: Guest access
|
||||||
Given I entered the course "Course 1" as "teacher1" in the app
|
Given I am on the "Course 1" "enrolment methods" page logged in as "teacher1"
|
||||||
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 press "Actions menu"
|
|
||||||
And I follow "More..."
|
|
||||||
And I follow "Users"
|
|
||||||
And I follow "Enrolment methods"
|
|
||||||
And I click on "Enable" "icon" in the "Guest access" "table_row"
|
And I click on "Enable" "icon" in the "Guest access" "table_row"
|
||||||
And I close the browser tab opened by the app
|
And I entered the app as "student2"
|
||||||
Given I entered the app as "student2"
|
|
||||||
When I press "Site home" in the app
|
When I press "Site home" in the app
|
||||||
And I press "Available courses" in the app
|
And I press "Available courses" in the app
|
||||||
And I press "Course 1" in the app
|
And I press "Course 1" in the app
|
||||||
|
|
|
@ -23,8 +23,7 @@ Feature: Test basic usage of guest access course in app
|
||||||
|
|
||||||
@lms_from4.0
|
@lms_from4.0
|
||||||
Scenario: Guest access without password (student)
|
Scenario: Guest access without password (student)
|
||||||
Given I log in as "teacher1"
|
Given I am on the "Course 1" "enrolment methods" page logged 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 click on "Edit" "link" in the "Guest access" "table_row"
|
||||||
And I set the following fields to these values:
|
And I set the following fields to these values:
|
||||||
| Allow guest access | Yes |
|
| Allow guest access | Yes |
|
||||||
|
@ -47,8 +46,7 @@ Feature: Test basic usage of guest access course in app
|
||||||
|
|
||||||
@lms_from4.3
|
@lms_from4.3
|
||||||
Scenario: Guest access with password (student)
|
Scenario: Guest access with password (student)
|
||||||
Given I log in as "teacher1"
|
Given I am on the "Course 1" "enrolment methods" page logged 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 click on "Edit" "link" in the "Guest access" "table_row"
|
||||||
And I set the following fields to these values:
|
And I set the following fields to these values:
|
||||||
| Allow guest access | Yes |
|
| Allow guest access | Yes |
|
||||||
|
|
|
@ -27,8 +27,6 @@ import { AddonEnrolSelf } from '@addons/enrol/self/services/self';
|
||||||
import { CoreEnrol, CoreEnrolEnrolmentInfo, CoreEnrolEnrolmentMethod } from '@features/enrol/services/enrol';
|
import { CoreEnrol, CoreEnrolEnrolmentInfo, CoreEnrolEnrolmentMethod } from '@features/enrol/services/enrol';
|
||||||
import { CoreSiteWSPreSets, WSObservable } from '@classes/sites/authenticated-site';
|
import { CoreSiteWSPreSets, WSObservable } from '@classes/sites/authenticated-site';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourses:';
|
|
||||||
|
|
||||||
declare module '@singletons/events' {
|
declare module '@singletons/events' {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +48,8 @@ declare module '@singletons/events' {
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class CoreCoursesProvider {
|
export class CoreCoursesProvider {
|
||||||
|
|
||||||
|
protected static readonly ROOT_CACHE_KEY = 'mmCourses:';
|
||||||
|
|
||||||
static readonly SEARCH_PER_PAGE = 20;
|
static readonly SEARCH_PER_PAGE = 20;
|
||||||
static readonly RECENT_PER_PAGE = 10;
|
static readonly RECENT_PER_PAGE = 10;
|
||||||
static readonly ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
|
static readonly ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
|
||||||
|
@ -114,7 +114,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean): string {
|
protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean): string {
|
||||||
return ROOT_CACHE_KEY + 'categories:' + categoryId + ':' + !!addSubcategories;
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}categories:${categoryId}:${!!addSubcategories}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,16 +131,16 @@ export class CoreCoursesProvider {
|
||||||
if (courseIds.length == 1) {
|
if (courseIds.length == 1) {
|
||||||
// Only 1 course, check if it belongs to the user courses. If so, use all user courses.
|
// Only 1 course, check if it belongs to the user courses. If so, use all user courses.
|
||||||
return this.getCourseIdsIfEnrolled(courseIds[0], siteId);
|
return this.getCourseIdsIfEnrolled(courseIds[0], siteId);
|
||||||
} else {
|
|
||||||
if (courseIds.length > 1 && courseIds.indexOf(siteHomeId) == -1) {
|
|
||||||
courseIds.push(siteHomeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the course IDs.
|
|
||||||
courseIds.sort((a, b) => b - a);
|
|
||||||
|
|
||||||
return courseIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (courseIds.length > 1 && courseIds.indexOf(siteHomeId) == -1) {
|
||||||
|
courseIds.push(siteHomeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the course IDs.
|
||||||
|
courseIds.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
return courseIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -363,7 +363,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getCoursesCacheKey(ids: number[]): string {
|
protected getCoursesCacheKey(ids: number[]): string {
|
||||||
return ROOT_CACHE_KEY + 'course:' + JSON.stringify(ids);
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}course:${JSON.stringify(ids)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -536,7 +536,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getCoursesByFieldCacheKey(field: string = '', value: string | number = ''): string {
|
protected getCoursesByFieldCacheKey(field: string = '', value: string | number = ''): string {
|
||||||
return ROOT_CACHE_KEY + 'coursesbyfield:' + field + ':' + value;
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}coursesbyfield:${field}:${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -651,7 +651,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getRecentCoursesCacheKey(userId: number): string {
|
protected getRecentCoursesCacheKey(userId: number): string {
|
||||||
return `${ROOT_CACHE_KEY}:recentcourses:${userId}`;
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}:recentcourses:${userId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -684,7 +684,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getUserAdministrationOptionsCommonCacheKey(): string {
|
protected getUserAdministrationOptionsCommonCacheKey(): string {
|
||||||
return ROOT_CACHE_KEY + 'administrationOptions:';
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}administrationOptions:`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -701,11 +701,14 @@ export class CoreCoursesProvider {
|
||||||
* Get user administration options for a set of courses.
|
* Get user administration options for a set of courses.
|
||||||
*
|
*
|
||||||
* @param courseIds IDs of courses to get.
|
* @param courseIds IDs of courses to get.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param options Options.
|
||||||
* @returns Promise resolved with administration options for each course.
|
* @returns Promise resolved with administration options for each course.
|
||||||
*/
|
*/
|
||||||
getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
getUserAdministrationOptions(
|
||||||
return firstValueFrom(this.getUserAdministrationOptionsObservable(courseIds, { siteId }));
|
courseIds: number[],
|
||||||
|
options?: CoreSitesCommonWSOptions,
|
||||||
|
): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||||
|
return firstValueFrom(this.getUserAdministrationOptionsObservable(courseIds, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -752,7 +755,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getUserNavigationOptionsCommonCacheKey(): string {
|
protected getUserNavigationOptionsCommonCacheKey(): string {
|
||||||
return ROOT_CACHE_KEY + 'navigationOptions:';
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}navigationOptions:`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -768,11 +771,14 @@ export class CoreCoursesProvider {
|
||||||
* Get user navigation options for a set of courses.
|
* Get user navigation options for a set of courses.
|
||||||
*
|
*
|
||||||
* @param courseIds IDs of courses to get.
|
* @param courseIds IDs of courses to get.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param options Options.
|
||||||
* @returns Promise resolved with navigation options for each course.
|
* @returns Promise resolved with navigation options for each course.
|
||||||
*/
|
*/
|
||||||
async getUserNavigationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
getUserNavigationOptions(
|
||||||
return firstValueFrom(this.getUserNavigationOptionsObservable(courseIds, { siteId }));
|
courseIds: number[],
|
||||||
|
options?: CoreSitesCommonWSOptions,
|
||||||
|
): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||||
|
return firstValueFrom(this.getUserNavigationOptionsObservable(courseIds, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -981,7 +987,7 @@ export class CoreCoursesProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getUserCoursesCacheKey(): string {
|
protected getUserCoursesCacheKey(): string {
|
||||||
return ROOT_CACHE_KEY + 'usercourses';
|
return `${CoreCoursesProvider.ROOT_CACHE_KEY}usercourses`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
CoreCourseSearchedData,
|
CoreCourseSearchedData,
|
||||||
CoreCourseUserAdminOrNavOptionIndexed,
|
CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
} from '@features/courses/services/courses';
|
} from '@features/courses/services/courses';
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseAccessDataType } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreGrades,
|
CoreGrades,
|
||||||
CoreGradesGradeItem,
|
CoreGradesGradeItem,
|
||||||
|
@ -680,11 +680,11 @@ export class CoreGradesHelperProvider {
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||||
return false; // Not enabled for guests.
|
return false; // Not enabled for guests.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.grades !== undefined) {
|
if (navOptions?.grades !== undefined) {
|
||||||
return navOptions.grades;
|
return navOptions.grades;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||||
|
|
||||||
if (navOptions && navOptions.grades !== undefined) {
|
if (navOptions?.grades !== undefined) {
|
||||||
// No need to invalidate user courses.
|
// No need to invalidate user courses.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export class CoreGradesCourseParticipantsOptionHandlerService implements CoreCou
|
||||||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||||
|
|
||||||
if (navOptions && navOptions.grades !== undefined) {
|
if (navOptions?.grades !== undefined) {
|
||||||
// No need to invalidate user courses.
|
// No need to invalidate user courses.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ export class CoreH5PCore {
|
||||||
toHash.sort((a, b) => a.localeCompare(b));
|
toHash.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
// Calculate hash.
|
// Calculate hash.
|
||||||
return <string> Md5.hashAsciiStr(toHash.join(''));
|
return Md5.hashAsciiStr(toHash.join(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1104,14 +1104,14 @@ export class CoreLoginHelperProvider {
|
||||||
|
|
||||||
// Validate the signature.
|
// Validate the signature.
|
||||||
// We need to check both http and https.
|
// We need to check both http and https.
|
||||||
let signature = <string> Md5.hashAsciiStr(launchSiteURL + passport);
|
let signature = Md5.hashAsciiStr(launchSiteURL + passport);
|
||||||
if (signature != params[0]) {
|
if (signature != params[0]) {
|
||||||
if (launchSiteURL.indexOf('https://') != -1) {
|
if (launchSiteURL.indexOf('https://') != -1) {
|
||||||
launchSiteURL = launchSiteURL.replace('https://', 'http://');
|
launchSiteURL = launchSiteURL.replace('https://', 'http://');
|
||||||
} else {
|
} else {
|
||||||
launchSiteURL = launchSiteURL.replace('http://', 'https://');
|
launchSiteURL = launchSiteURL.replace('http://', 'https://');
|
||||||
}
|
}
|
||||||
signature = <string> Md5.hashAsciiStr(launchSiteURL + passport);
|
signature = Md5.hashAsciiStr(launchSiteURL + passport);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signature == params[0]) {
|
if (signature == params[0]) {
|
||||||
|
|
|
@ -123,7 +123,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = <string> Md5.hashAsciiStr(path);
|
const hash = Md5.hashAsciiStr(path);
|
||||||
|
|
||||||
CoreNavigator.navigate(`../${hash}`, {
|
CoreNavigator.navigate(`../${hash}`, {
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -151,7 +151,7 @@ export class CoreSharedFilesProvider {
|
||||||
* @returns File ID.
|
* @returns File ID.
|
||||||
*/
|
*/
|
||||||
protected getFileId(entry: FileEntry): string {
|
protected getFileId(entry: FileEntry): string {
|
||||||
return <string> Md5.hashAsciiStr(entry.name);
|
return Md5.hashAsciiStr(entry.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl
|
||||||
const args = {
|
const args = {
|
||||||
courseid: course.id,
|
courseid: course.id,
|
||||||
};
|
};
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: this.title,
|
title: this.title,
|
||||||
|
|
|
@ -24,13 +24,13 @@ import {
|
||||||
CoreSitePluginsContent,
|
CoreSitePluginsContent,
|
||||||
CoreSitePluginsCourseModuleHandlerData,
|
CoreSitePluginsCourseModuleHandlerData,
|
||||||
CoreSitePluginsPlugin,
|
CoreSitePluginsPlugin,
|
||||||
CoreSitePluginsProvider,
|
|
||||||
} from '@features/siteplugins/services/siteplugins';
|
} from '@features/siteplugins/services/siteplugins';
|
||||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreSitePluginsBaseHandler } from './base-handler';
|
import { CoreSitePluginsBaseHandler } from './base-handler';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '@features/siteplugins/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to support a module using a site plugin.
|
* Handler to support a module using a site plugin.
|
||||||
|
@ -114,7 +114,7 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
|
||||||
this.loadCoursePageTemplate(module, courseId, handlerData, method);
|
this.loadCoursePageTemplate(module, courseId, handlerData, method);
|
||||||
|
|
||||||
// Allow updating the data via event.
|
// Allow updating the data via event.
|
||||||
CoreEvents.on(CoreSitePluginsProvider.UPDATE_COURSE_CONTENT, (data) => {
|
CoreEvents.on(CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT, (data) => {
|
||||||
if (data.cmId === module.id) {
|
if (data.cmId === module.id) {
|
||||||
this.loadCoursePageTemplate(module, courseId, handlerData, method, !data.alreadyFetched);
|
this.loadCoursePageTemplate(module, courseId, handlerData, method, !data.alreadyFetched);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
|
||||||
courseid: contextId,
|
courseid: contextId,
|
||||||
userid: user.id,
|
userid: user.id,
|
||||||
};
|
};
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
`siteplugins/content/${this.plugin.component}/${this.handlerSchema.method}/${hash}`,
|
`siteplugins/content/${this.plugin.component}/${this.handlerSchema.method}/${hash}`,
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class CoreSitePluginsOnlyTitleBlockComponent extends CoreBlockBaseCompone
|
||||||
contextlevel: this.contextLevel,
|
contextlevel: this.contextLevel,
|
||||||
instanceid: this.instanceId,
|
instanceid: this.instanceId,
|
||||||
};
|
};
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(
|
CoreNavigator.navigateToSitePath(
|
||||||
`siteplugins/content/${handler.plugin.component}/${handler.handlerSchema.method}/${hash}`,
|
`siteplugins/content/${handler.plugin.component}/${handler.handlerSchema.method}/${hash}`,
|
||||||
|
|
|
@ -29,11 +29,12 @@ import { Md5 } from 'ts-md5';
|
||||||
|
|
||||||
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
||||||
import { CoreCompileHtmlComponent } from '@features/compile/components/compile-html/compile-html';
|
import { CoreCompileHtmlComponent } from '@features/compile/components/compile-html/compile-html';
|
||||||
import { CoreSitePlugins, CoreSitePluginsContent, CoreSitePluginsProvider } from '@features/siteplugins/services/siteplugins';
|
import { CoreSitePlugins, CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
|
import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '@features/siteplugins/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to render a site plugin content.
|
* Component to render a site plugin content.
|
||||||
|
@ -181,7 +182,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
||||||
|
|
||||||
component = component || this.component;
|
component = component || this.component;
|
||||||
method = method || this.method;
|
method = method || this.method;
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
||||||
params: {
|
params: {
|
||||||
|
@ -261,7 +262,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
||||||
* @param alreadyFetched Whether course data has already been fetched (no need to fetch it again).
|
* @param alreadyFetched Whether course data has already been fetched (no need to fetch it again).
|
||||||
*/
|
*/
|
||||||
updateModuleCourseContent(cmId: number, alreadyFetched?: boolean): void {
|
updateModuleCourseContent(cmId: number, alreadyFetched?: boolean): void {
|
||||||
CoreEvents.trigger(CoreSitePluginsProvider.UPDATE_COURSE_CONTENT, { cmId, alreadyFetched });
|
CoreEvents.trigger(CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT, { cmId, alreadyFetched });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
export const CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT = 'siteplugins_update_course_content';
|
|
@ -99,7 +99,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal
|
||||||
} else {
|
} else {
|
||||||
const component = this.component || this.parentContent?.component;
|
const component = this.component || this.parentContent?.component;
|
||||||
const method = this.method || this.parentContent?.method;
|
const method = this.method || this.parentContent?.method;
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -98,7 +98,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit {
|
||||||
} else {
|
} else {
|
||||||
const component = this.component || this.parentContent?.component;
|
const component = this.component || this.parentContent?.component;
|
||||||
const method = this.method || this.parentContent?.method;
|
const method = this.method || this.parentContent?.method;
|
||||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -268,13 +268,13 @@ export class CoreSitePluginsHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a "fake" instance to hold all the libraries.
|
// Create a "fake" instance to hold all the libraries.
|
||||||
const lazyLibraries = await CoreCompile.getLazyLibraries();
|
|
||||||
const instance = {
|
const instance = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
HANDLER_DISABLED: HANDLER_DISABLED,
|
HANDLER_DISABLED: HANDLER_DISABLED,
|
||||||
};
|
};
|
||||||
|
|
||||||
CoreCompile.injectLibraries(instance, lazyLibraries);
|
await CoreCompile.loadLibraries();
|
||||||
|
CoreCompile.injectLibraries(instance);
|
||||||
|
|
||||||
// Add some data of the WS call result.
|
// Add some data of the WS call result.
|
||||||
const jsData = CoreSitePlugins.createDataForJS(result);
|
const jsData = CoreSitePlugins.createDataForJS(result);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { CorePlatform } from '@services/platform';
|
||||||
import { CoreEnrolAction, CoreEnrolInfoIcon } from '@features/enrol/services/enrol-delegate';
|
import { CoreEnrolAction, CoreEnrolInfoIcon } from '@features/enrol/services/enrol-delegate';
|
||||||
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
||||||
import { CoreUserProfileHandlerType } from '@features/user/services/user-delegate';
|
import { CoreUserProfileHandlerType } from '@features/user/services/user-delegate';
|
||||||
|
import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '../constants';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
||||||
export class CoreSitePluginsProvider {
|
export class CoreSitePluginsProvider {
|
||||||
|
|
||||||
static readonly COMPONENT = 'CoreSitePlugins';
|
static readonly COMPONENT = 'CoreSitePlugins';
|
||||||
static readonly UPDATE_COURSE_CONTENT = 'siteplugins_update_course_content';
|
static readonly UPDATE_COURSE_CONTENT = CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT;
|
||||||
|
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected sitePlugins: {[name: string]: CoreSitePluginsHandler} = {}; // Site plugins registered.
|
protected sitePlugins: {[name: string]: CoreSitePluginsHandler} = {}; // Site plugins registered.
|
||||||
|
@ -995,7 +996,7 @@ declare module '@singletons/events' {
|
||||||
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||||
*/
|
*/
|
||||||
export interface CoreEventsData {
|
export interface CoreEventsData {
|
||||||
[CoreSitePluginsProvider.UPDATE_COURSE_CONTENT]: CoreSitePluginsUpdateCourseContentEvent;
|
[CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT]: CoreSitePluginsUpdateCourseContentEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,42 @@ import { canLeaveGuard } from '@guards/can-leave';
|
||||||
import { CoreSitePluginsCourseOptionPage } from '@features/siteplugins/pages/course-option/course-option';
|
import { CoreSitePluginsCourseOptionPage } from '@features/siteplugins/pages/course-option/course-option';
|
||||||
import { CoreSitePluginsModuleIndexPage } from '@features/siteplugins/pages/module-index/module-index';
|
import { CoreSitePluginsModuleIndexPage } from '@features/siteplugins/pages/module-index/module-index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get site plugins exported objects.
|
||||||
|
*
|
||||||
|
* @returns Site plugins exported objects.
|
||||||
|
*/
|
||||||
|
export async function getSitePluginsExportedObjects(): Promise<Record<string, unknown>> {
|
||||||
|
const { CoreSitePluginsModuleIndexComponent } = await import ('@features/siteplugins/components/module-index/module-index');
|
||||||
|
const { CoreSitePluginsBlockComponent } = await import ('@features/siteplugins/components/block/block');
|
||||||
|
const { CoreSitePluginsCourseFormatComponent } = await import ('@features/siteplugins/components/course-format/course-format');
|
||||||
|
const { CoreSitePluginsQuestionComponent } = await import ('@features/siteplugins/components/question/question');
|
||||||
|
const { CoreSitePluginsQuestionBehaviourComponent }
|
||||||
|
= await import ('@features/siteplugins/components/question-behaviour/question-behaviour');
|
||||||
|
const { CoreSitePluginsUserProfileFieldComponent }
|
||||||
|
= await import ('@features/siteplugins/components/user-profile-field/user-profile-field');
|
||||||
|
const { CoreSitePluginsQuizAccessRuleComponent }
|
||||||
|
= await import ('@features/siteplugins/components/quiz-access-rule/quiz-access-rule');
|
||||||
|
const { CoreSitePluginsAssignFeedbackComponent }
|
||||||
|
= await import ('@features/siteplugins/components/assign-feedback/assign-feedback');
|
||||||
|
const { CoreSitePluginsAssignSubmissionComponent }
|
||||||
|
= await import ('@features/siteplugins/components/assign-submission/assign-submission');
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
return {
|
||||||
|
CoreSitePluginsModuleIndexComponent,
|
||||||
|
CoreSitePluginsBlockComponent,
|
||||||
|
CoreSitePluginsCourseFormatComponent,
|
||||||
|
CoreSitePluginsQuestionComponent,
|
||||||
|
CoreSitePluginsQuestionBehaviourComponent,
|
||||||
|
CoreSitePluginsUserProfileFieldComponent,
|
||||||
|
CoreSitePluginsQuizAccessRuleComponent,
|
||||||
|
CoreSitePluginsAssignFeedbackComponent,
|
||||||
|
CoreSitePluginsAssignSubmissionComponent,
|
||||||
|
};
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
}
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'siteplugins/content/:component/:method/:hash',
|
path: 'siteplugins/content/:component/:method/:hash',
|
||||||
|
|
|
@ -218,7 +218,7 @@ export class CoreStylesService {
|
||||||
contents = (await handler.getStyle(siteId, config)).trim();
|
contents = (await handler.getStyle(siteId, config)).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = <string>Md5.hashAsciiStr(contents);
|
const hash = Md5.hashAsciiStr(contents);
|
||||||
|
|
||||||
// Update the styles only if they have changed.
|
// Update the styles only if they have changed.
|
||||||
if (this.stylesEls[siteId][handler.name] === hash) {
|
if (this.stylesEls[siteId][handler.name] === hash) {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseAccess,
|
CoreCourseAccess,
|
||||||
CoreCourseOptionsHandler,
|
CoreCourseOptionsHandler,
|
||||||
|
@ -37,7 +37,7 @@ export class CoreUserCourseOptionHandlerService implements CoreCourseOptionsHand
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
if (navOptions && navOptions.participants !== undefined) {
|
if (navOptions?.participants !== undefined) {
|
||||||
// No need to invalidate anything.
|
// No need to invalidate anything.
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@ export class CoreUserCourseOptionHandlerService implements CoreCourseOptionsHand
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): boolean | Promise<boolean> {
|
): boolean | Promise<boolean> {
|
||||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||||
return false; // Not enabled for guests.
|
return false; // Not enabled for guests.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.participants !== undefined) {
|
if (navOptions?.participants !== undefined) {
|
||||||
return navOptions.participants;
|
return navOptions.participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1303,7 +1303,7 @@ export class CoreFilepoolProvider {
|
||||||
* @returns File download ID.
|
* @returns File download ID.
|
||||||
*/
|
*/
|
||||||
protected getFileDownloadId(fileUrl: string, filePath: string): string {
|
protected getFileDownloadId(fileUrl: string, filePath: string): string {
|
||||||
return <string> Md5.hashAsciiStr(fileUrl + '###' + filePath);
|
return Md5.hashAsciiStr(fileUrl + '###' + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1867,7 +1867,7 @@ export class CoreFilepoolProvider {
|
||||||
* @returns Package ID.
|
* @returns Package ID.
|
||||||
*/
|
*/
|
||||||
getPackageId(component: string, componentId?: string | number): string {
|
getPackageId(component: string, componentId?: string | number): string {
|
||||||
return <string> Md5.hashAsciiStr(component + '#' + this.fixComponentId(componentId));
|
return Md5.hashAsciiStr(component + '#' + this.fixComponentId(componentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -721,7 +721,7 @@ export class CoreSitesProvider {
|
||||||
* @returns Site ID.
|
* @returns Site ID.
|
||||||
*/
|
*/
|
||||||
createSiteID(siteUrl: string, username: string): string {
|
createSiteID(siteUrl: string, username: string): string {
|
||||||
return <string> Md5.hashAsciiStr(siteUrl + username);
|
return Md5.hashAsciiStr(siteUrl + username);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1456,7 +1456,7 @@ export class CoreDomUtilsProvider {
|
||||||
const listenCloseEvents = closeOnNavigate ?? true; // Default to true.
|
const listenCloseEvents = closeOnNavigate ?? true; // Default to true.
|
||||||
|
|
||||||
// TODO: Improve this if we need two modals with same component open at the same time.
|
// TODO: Improve this if we need two modals with same component open at the same time.
|
||||||
const modalId = <string> Md5.hashAsciiStr(options.component?.toString() || '');
|
const modalId = Md5.hashAsciiStr(options.component?.toString() || '');
|
||||||
|
|
||||||
const modal = this.displayedModals[modalId]
|
const modal = this.displayedModals[modalId]
|
||||||
? this.displayedModals[modalId]
|
? this.displayedModals[modalId]
|
||||||
|
|
|
@ -4,22 +4,22 @@ Feature: It opens external links properly.
|
||||||
Background:
|
Background:
|
||||||
Given the following "users" exist:
|
Given the following "users" exist:
|
||||||
| username |
|
| username |
|
||||||
| student1 |
|
| teacher1 |
|
||||||
And the following "courses" exist:
|
And the following "courses" exist:
|
||||||
| fullname | shortname |
|
| fullname | shortname |
|
||||||
| Course 1 | C1 |
|
| Course 1 | C1 |
|
||||||
And the following "course enrolments" exist:
|
And the following "course enrolments" exist:
|
||||||
| user | course | role |
|
| user | course | role |
|
||||||
| student1 | C1 | student |
|
| teacher1 | C1 | teacher |
|
||||||
And the following "activities" exist:
|
And the following "activities" exist:
|
||||||
| activity | name | intro | course | idnumber |
|
| activity | name | intro | course | idnumber |
|
||||||
| forum | Test forum | Test forum | C1 | forum |
|
| forum | Test forum | Test forum | C1 | forum |
|
||||||
And the following forum discussions exist in course "Course 1":
|
And the following forum discussions exist in course "Course 1":
|
||||||
| forum | user | name | message |
|
| forum | user | name | message |
|
||||||
| Test forum | student1 | Forum topic | See <a href="https://moodle.org/">moodle.org external link</a> |
|
| Test forum | teacher1 | Forum topic | See <a href="https://moodle.org/">moodle.org external link</a> |
|
||||||
|
|
||||||
Scenario: Click an external link
|
Scenario: Click an external link
|
||||||
Given I entered the forum activity "Test forum" on course "Course 1" as "student1" in the app
|
Given I entered the forum activity "Test forum" on course "Course 1" as "teacher1" in the app
|
||||||
When I press "Forum topic" in the app
|
When I press "Forum topic" in the app
|
||||||
And I press "moodle.org external link" in the app
|
And I press "moodle.org external link" in the app
|
||||||
Then I should find "You are about to leave the app" in the app
|
Then I should find "You are about to leave the app" in the app
|
||||||
|
|
Loading…
Reference in New Issue