commit
3036d096aa
|
@ -64,7 +64,7 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (navOptions && navOptions.badges !== undefined) {
|
||||
if (navOptions?.badges !== undefined) {
|
||||
return navOptions.badges;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export class AddonBlogCourseOptionHandlerService implements CoreCourseOptionsHan
|
|||
): Promise<boolean> {
|
||||
const enabled = await CoreCourseHelper.hasABlockNamed(courseId, 'blog_menu');
|
||||
|
||||
if (enabled && navOptions && navOptions.blogs !== undefined) {
|
||||
if (enabled && navOptions?.blogs !== undefined) {
|
||||
return navOptions.blogs;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||
import {
|
||||
CoreCourseAccess,
|
||||
CoreCourseOptionsHandler,
|
||||
|
@ -50,11 +50,11 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti
|
|||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
if (accessData && accessData.type === CoreCourseProvider.ACCESS_GUEST) {
|
||||
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||
return false; // Not enabled for guest access.
|
||||
}
|
||||
|
||||
if (navOptions && navOptions.competencies !== undefined) {
|
||||
if (navOptions?.competencies !== undefined) {
|
||||
return navOptions.competencies;
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti
|
|||
* @inheritdoc
|
||||
*/
|
||||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||
if (navOptions && navOptions.competencies !== undefined) {
|
||||
if (navOptions?.competencies !== undefined) {
|
||||
// No need to invalidate anything.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||
import {
|
||||
CoreCourseAccess,
|
||||
CoreCourseOptionsHandler,
|
||||
|
@ -43,7 +43,7 @@ export class AddonCourseCompletionCourseOptionHandlerService implements CoreCour
|
|||
* @inheritdoc
|
||||
*/
|
||||
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.
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
# Join the session as moderator in a browser.
|
||||
When I press "Information" 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"
|
||||
When I open a browser tab with url "$WWWROOT"
|
||||
And I am on the "bbb1" Activity page logged in as teacher1
|
||||
And I click on "Join session" "link"
|
||||
And I wait for the BigBlueButton room to start
|
||||
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
|
||||
Then I should find "1" near "Moderator" 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
|
||||
Then I should find "Test choice description" in the app
|
||||
|
||||
When I press "Information" 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"
|
||||
When I open a browser tab with url "$WWWROOT"
|
||||
And I am on the "choice1" Activity page logged in as teacher1
|
||||
And I press "Actions menu"
|
||||
And I follow "View 1 responses"
|
||||
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
|
||||
Then I should find "Test choice description" in the app
|
||||
|
||||
When I press "Information" 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"
|
||||
When I open a browser tab with url "$WWWROOT"
|
||||
And I am on the "choice1" Activity page logged in as teacher1
|
||||
And I follow "Responses"
|
||||
And I press "Download in text format"
|
||||
# TODO Then I should find "..." in the downloads folder
|
||||
|
|
|
@ -138,7 +138,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
|||
subfolder: folder,
|
||||
};
|
||||
|
||||
const hash = <string> Md5.hashAsciiStr(folder.filepath);
|
||||
const hash = Md5.hashAsciiStr(folder.filepath);
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`${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]"
|
||||
Then the UI should match the snapshot
|
||||
|
||||
Given I entered the quiz activity "Quiz 1" on course "Course 1" as "teacher1" in the app
|
||||
When I press "Information" 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 follow "Attempts: 1"
|
||||
Given I open a browser tab with url "$WWWROOT"
|
||||
And I am on the "quiz1" Activity page logged in as teacher1
|
||||
When I follow "Attempts: 1"
|
||||
And I follow "Review attempt"
|
||||
Then I should see "Finished"
|
||||
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]"
|
||||
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
|
||||
When I press "Information" 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"
|
||||
Given I open a browser tab with url "$WWWROOT"
|
||||
When I am on the "quiz1" Activity page logged in as teacher1
|
||||
And I follow "Attempts: 1"
|
||||
And I follow "Review attempt"
|
||||
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]"
|
||||
Then the UI should match the snapshot
|
||||
|
||||
Given I entered the quiz activity "Quiz 1" on course "Course 1" as "teacher1" in the app
|
||||
When I press "Information" 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 follow "Attempts: 1"
|
||||
Given I open a browser tab with url "$WWWROOT"
|
||||
And I am on the "quiz1" Activity page logged in as teacher1
|
||||
When I follow "Attempts: 1"
|
||||
And I follow "Review attempt"
|
||||
Then I should see "Finished"
|
||||
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
|
||||
|
||||
When I open a browser tab with url "$WWWROOT"
|
||||
And I log in as "admin"
|
||||
And I am on the "System logs report" page
|
||||
And I am on the "System logs report" page logged in as "admin"
|
||||
And I set the field "id" to "Course 1"
|
||||
And I set the field "user" to "Student student"
|
||||
And I press "Get these logs"
|
||||
|
|
|
@ -638,7 +638,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
|||
* @returns Promise.
|
||||
*/
|
||||
protected async openPageOrSubwiki(options: AddonModWikiOpenPageOptions): Promise<void> {
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify({
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify({
|
||||
...options,
|
||||
timestamp: Date.now(),
|
||||
}));
|
||||
|
|
|
@ -59,7 +59,7 @@ export class AddonModWikiPageOrMapLinkHandlerService extends CoreContentLinksHan
|
|||
{ siteId, readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
|
||||
);
|
||||
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify({
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify({
|
||||
pageId: page.id,
|
||||
pageTitle: page.title,
|
||||
subwikiId: page.subwikiid,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||
import {
|
||||
CoreCourseAccess,
|
||||
CoreCourseOptionsHandler,
|
||||
|
@ -47,11 +47,11 @@ export class AddonNotesCourseOptionHandlerService implements CoreCourseOptionsHa
|
|||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
if (accessData && accessData.type === CoreCourseProvider.ACCESS_GUEST) {
|
||||
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||
return false; // Not enabled for guest access.
|
||||
}
|
||||
|
||||
if (navOptions && navOptions.notes !== undefined) {
|
||||
if (navOptions?.notes !== undefined) {
|
||||
return navOptions.notes;
|
||||
}
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy {
|
|||
params.filename = '';
|
||||
}
|
||||
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(params));
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify(params));
|
||||
|
||||
CoreNavigator.navigate(`../${hash}`, { params });
|
||||
}
|
||||
|
|
|
@ -12,32 +12,40 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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';
|
||||
import { CoreWSError } from './wserror';
|
||||
import { CoreCanceledError } from './cancelederror';
|
||||
import { CoreSilentError } from './silenterror';
|
||||
import { CoreAjaxError } from './ajaxerror';
|
||||
import { CoreAjaxWSError } from './ajaxwserror';
|
||||
import { CoreCaptureError } from './captureerror';
|
||||
import { CoreNetworkError } from './network-error';
|
||||
import { CoreSiteError } from './siteerror';
|
||||
import { CoreLoginError } from './loginerror';
|
||||
import { CoreErrorWithOptions } from './errorwithoptions';
|
||||
import { CoreHttpError } from './httperror';
|
||||
const { CoreError } = await import('./error');
|
||||
const { CoreWSError } = await import('./wserror');
|
||||
const { CoreCanceledError } = await import('./cancelederror');
|
||||
const { CoreSilentError } = await import('./silenterror');
|
||||
const { CoreAjaxError } = await import('./ajaxerror');
|
||||
const { CoreAjaxWSError } = await import('./ajaxwserror');
|
||||
const { CoreCaptureError } = await import('./captureerror');
|
||||
const { CoreNetworkError } = await import('./network-error');
|
||||
const { CoreSiteError } = await import('./siteerror');
|
||||
const { CoreLoginError } = await import('./loginerror');
|
||||
const { CoreErrorWithOptions } = await import('./errorwithoptions');
|
||||
const { CoreHttpError } = await import('./httperror');
|
||||
|
||||
export const CORE_ERRORS_CLASSES: Type<unknown>[] = [
|
||||
CoreAjaxError,
|
||||
CoreAjaxWSError,
|
||||
CoreCanceledError,
|
||||
CoreCaptureError,
|
||||
CoreError,
|
||||
CoreNetworkError,
|
||||
CoreSilentError,
|
||||
CoreSiteError,
|
||||
CoreLoginError,
|
||||
CoreWSError,
|
||||
CoreErrorWithOptions,
|
||||
CoreHttpError,
|
||||
];
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
return {
|
||||
CoreError,
|
||||
CoreWSError,
|
||||
CoreCanceledError,
|
||||
CoreSilentError,
|
||||
CoreAjaxError,
|
||||
CoreAjaxWSError,
|
||||
CoreCaptureError,
|
||||
CoreNetworkError,
|
||||
CoreSiteError,
|
||||
CoreLoginError,
|
||||
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
|
||||
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>> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const compileInstance = this;
|
||||
const lazyLibraries = await CoreCompile.getLazyLibraries();
|
||||
await CoreCompile.loadLibraries();
|
||||
|
||||
// Create the component, using the text as the template.
|
||||
return class CoreCompileHtmlFakeComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy {
|
||||
|
@ -187,10 +187,10 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
this['dataArray'] = [];
|
||||
|
||||
// Inject the libraries.
|
||||
CoreCompile.injectLibraries(this, [
|
||||
...lazyLibraries,
|
||||
...compileInstance.extraProviders,
|
||||
]);
|
||||
CoreCompile.injectLibraries(
|
||||
this,
|
||||
compileInstance.extraProviders,
|
||||
);
|
||||
|
||||
// Always add these elements, they could be needed on component init (componentObservable).
|
||||
this['ChangeDetectorRef'] = compileInstance.changeDetector;
|
||||
|
|
|
@ -43,8 +43,8 @@ import { makeSingleton } from '@singletons';
|
|||
import { getCoreServices } from '@/core/core.module';
|
||||
import { getBlockServices } from '@features/block/block.module';
|
||||
import { getCommentsServices } from '@features/comments/comments.module';
|
||||
import { getContentLinksServices } from '@features/contentlinks/contentlinks.module';
|
||||
import { getCourseServices } from '@features/course/course.module';
|
||||
import { getContentLinksExportedObjects, getContentLinksServices } from '@features/contentlinks/contentlinks.module';
|
||||
import { getCourseExportedObjects, getCourseServices } from '@features/course/course.module';
|
||||
import { getCoursesServices } from '@features/courses/courses.module';
|
||||
import { getEditorServices } from '@features/editor/editor.module';
|
||||
import { getEnrolServices } from '@features/enrol/enrol.module';
|
||||
|
@ -88,13 +88,8 @@ import { CoreUrl } from '@singletons/url';
|
|||
import { CoreWindow } from '@singletons/window';
|
||||
import { CoreCache } from '@classes/cache';
|
||||
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 { CORE_ERRORS_CLASSES } from '@classes/errors/errors';
|
||||
import { getCoreErrorsExportedObjects } from '@classes/errors/errors';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
|
||||
// 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 { 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 { getBadgesServices } from '@addons/badges/badges.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 { 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.
|
||||
|
@ -177,6 +161,9 @@ export class CoreCompileProvider {
|
|||
getModWorkshopComponentModules,
|
||||
];
|
||||
|
||||
protected libraries?: unknown[];
|
||||
protected exportedObjects?: Record<string, unknown>;
|
||||
|
||||
constructor(protected injector: Injector) {
|
||||
this.logger = CoreLogger.getInstance('CoreCompileProvider');
|
||||
}
|
||||
|
@ -264,26 +251,28 @@ export class CoreCompileProvider {
|
|||
* Inject all the core libraries in a certain object.
|
||||
*
|
||||
* @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
|
||||
injectLibraries(instance: any, extraProviders: Type<unknown>[] = []): void {
|
||||
const providers = [
|
||||
...extraProviders,
|
||||
CoreAutoLogoutService,
|
||||
CoreSitePluginsProvider,
|
||||
...this.OTHER_SERVICES,
|
||||
injectLibraries(instance: any, extraLibraries: Type<unknown>[] = []): void {
|
||||
if (!this.libraries || !this.exportedObjects) {
|
||||
throw new CoreError('Libraries not loaded. You need to call loadLibraries before calling injectLibraries.');
|
||||
}
|
||||
|
||||
const libraries = [
|
||||
...this.libraries,
|
||||
...extraLibraries,
|
||||
];
|
||||
|
||||
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
||||
for (const i in providers) {
|
||||
const providerDef = providers[i];
|
||||
if (typeof providerDef === 'function' && providerDef.name) {
|
||||
for (const i in libraries) {
|
||||
const libraryDef = libraries[i];
|
||||
if (typeof libraryDef === 'function' && libraryDef.name) {
|
||||
try {
|
||||
// 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) {
|
||||
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['CoreDelegate'] = CoreDelegate;
|
||||
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['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.
|
||||
*/
|
||||
async getLazyLibraries(): Promise<Type<unknown>[]> {
|
||||
protected async getLibraries(): Promise<unknown[]> {
|
||||
const services = await Promise.all([
|
||||
getCoreServices(),
|
||||
getBlockServices(),
|
||||
|
@ -389,7 +377,30 @@ export class CoreCompileProvider {
|
|||
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({
|
||||
imports: [
|
||||
CoreContentLinksComponentsModule,
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
</h1>
|
||||
</ion-label>
|
||||
<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-button>
|
||||
</ion-item>
|
||||
|
|
|
@ -39,3 +39,7 @@ ion-item.card-header {
|
|||
margin: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.core-module-oib-button.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
course?: CoreEnrolledCourseData;
|
||||
modicon = '';
|
||||
moduleNameTranslated = '';
|
||||
isTeacher = false;
|
||||
|
||||
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
||||
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
|
||||
|
@ -269,13 +270,14 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
|||
* Fetch course.
|
||||
*/
|
||||
protected async fetchCourse(): Promise<void> {
|
||||
// Fix that.
|
||||
try {
|
||||
this.course = await CoreCourses.getUserCourse(this.courseId, true);
|
||||
} catch {
|
||||
// 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.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 CONTENTS_PAGE_NAME = 'contents';
|
||||
export const COURSE_CONTENTS_PATH = `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${CONTENTS_PAGE_NAME}`;
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
</ion-chip>
|
||||
</ion-label>
|
||||
<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-button>
|
||||
</ion-item>
|
||||
|
|
|
@ -73,6 +73,7 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
progress?: number;
|
||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||
displayOpenInBrowser = false;
|
||||
isTeacher = false;
|
||||
|
||||
protected actionSheet?: HTMLIonActionSheetElement;
|
||||
protected waitStart = 0;
|
||||
|
@ -172,6 +173,9 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -254,7 +258,7 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadMenuHandlers(refresh?: boolean): Promise<void> {
|
||||
if (!this.course) {
|
||||
if (!this.course || !this.canAccessCourse) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -104,4 +104,8 @@
|
|||
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);
|
||||
|
|
|
@ -24,10 +24,9 @@ import {
|
|||
CoreCoursesProvider,
|
||||
CoreCourseUserAdminOrNavOptionIndexed,
|
||||
} from '@features/courses/services/courses';
|
||||
import { CoreCourseProvider } from './course';
|
||||
import { CoreCourseAccessDataType } from './course';
|
||||
import { Params } from '@angular/router';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||
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.
|
||||
* @returns Promise resolved with array of handlers.
|
||||
*/
|
||||
protected async getHandlersForAccess(
|
||||
protected async updateHandlersForAccess(
|
||||
courseId: number,
|
||||
refresh: boolean,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<CoreCourseOptionsHandler[]> {
|
||||
): Promise<void> {
|
||||
|
||||
// If the handlers aren't loaded, do not refresh.
|
||||
if (!this.loaded[courseId]) {
|
||||
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]) {
|
||||
this.coursesHandlers[courseId] = {
|
||||
access: accessData,
|
||||
|
@ -347,8 +346,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
}
|
||||
|
||||
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 refresh True if it should refresh the list.
|
||||
* @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.
|
||||
*/
|
||||
getHandlersToDisplay(
|
||||
course: CoreCourseAnyCourseData,
|
||||
refresh = false,
|
||||
isGuest = false,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<CoreCourseOptionsHandlerToDisplay[]> {
|
||||
return this.getHandlersToDisplayInternal(false, course, refresh, isGuest, navOptions, admOptions) as
|
||||
return this.getHandlersToDisplayInternal(false, course, refresh, isGuest) as
|
||||
Promise<CoreCourseOptionsHandlerToDisplay[]>;
|
||||
}
|
||||
|
||||
|
@ -380,18 +373,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
* @param course The course object.
|
||||
* @param refresh True if it should refresh the list.
|
||||
* @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.
|
||||
*/
|
||||
getMenuHandlersToDisplay(
|
||||
course: CoreCourseAnyCourseData,
|
||||
refresh = false,
|
||||
isGuest = false,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<CoreCourseOptionsMenuHandlerToDisplay[]> {
|
||||
return this.getHandlersToDisplayInternal(true, course, refresh, isGuest, navOptions, admOptions) as
|
||||
return this.getHandlersToDisplayInternal(true, course, refresh, isGuest) as
|
||||
Promise<CoreCourseOptionsMenuHandlerToDisplay[]>;
|
||||
}
|
||||
|
||||
|
@ -403,8 +392,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
* @param course The course object.
|
||||
* @param refresh True if it should refresh the list.
|
||||
* @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.
|
||||
*/
|
||||
protected async getHandlersToDisplayInternal(
|
||||
|
@ -412,36 +399,30 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
course: CoreCourseAnyCourseData,
|
||||
refresh = false,
|
||||
isGuest = false,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[]> {
|
||||
|
||||
const courseWithOptions: CoreCourseAnyCourseDataWithOptions = course;
|
||||
const accessData = {
|
||||
type: isGuest ? CoreCourseProvider.ACCESS_GUEST : CoreCourseProvider.ACCESS_DEFAULT,
|
||||
type: isGuest ? CoreCourseAccessDataType.ACCESS_GUEST : CoreCourseAccessDataType.ACCESS_DEFAULT,
|
||||
};
|
||||
const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||
|
||||
if (navOptions) {
|
||||
courseWithOptions.navOptions = navOptions;
|
||||
}
|
||||
if (admOptions) {
|
||||
courseWithOptions.admOptions = admOptions;
|
||||
}
|
||||
|
||||
await this.loadCourseOptions(courseWithOptions, refresh);
|
||||
|
||||
// Call getHandlersForAccess to make sure the handlers have been loaded.
|
||||
await this.getHandlersForAccess(course.id, refresh, accessData, courseWithOptions.navOptions, courseWithOptions.admOptions);
|
||||
// Call updateHandlersForAccess to make sure the handlers have been loaded.
|
||||
await this.updateHandlersForAccess(
|
||||
course.id,
|
||||
refresh,
|
||||
accessData,
|
||||
courseWithOptions.navOptions,
|
||||
courseWithOptions.admOptions,
|
||||
);
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
let handlerList: CoreCourseOptionsMenuHandler[] | CoreCourseOptionsHandler[];
|
||||
if (menu) {
|
||||
handlerList = this.coursesHandlers[course.id].enabledMenuHandlers;
|
||||
} else {
|
||||
handlerList = this.coursesHandlers[course.id].enabledHandlers;
|
||||
}
|
||||
const handlerList = menu
|
||||
? this.coursesHandlers[course.id].enabledMenuHandlers
|
||||
: this.coursesHandlers[course.id].enabledHandlers;
|
||||
|
||||
handlerList.forEach((handler: CoreCourseOptionsMenuHandler | CoreCourseOptionsHandler) => {
|
||||
const getFunction = menu
|
||||
|
@ -461,8 +442,8 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
});
|
||||
|
||||
return;
|
||||
}).catch((err) => {
|
||||
this.logger.error('Error getting data for handler', handler.name, err);
|
||||
}).catch((error) => {
|
||||
this.logger.error(`Error getting data for handler ${handler.name}`, error);
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -477,17 +458,44 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
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.
|
||||
*
|
||||
* @param course The course object.
|
||||
* @param refresh True if it should refresh the list.
|
||||
* @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.
|
||||
await this.loadCourseOptions(course, refresh);
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
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 admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @returns Promise resolved with boolean: true if it has handlers, false otherwise.
|
||||
* @deprecated since 4.4.
|
||||
*/
|
||||
async hasHandlersForDefault(
|
||||
courseId: number,
|
||||
|
@ -506,14 +515,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
// Default access.
|
||||
const accessData = {
|
||||
type: CoreCourseProvider.ACCESS_DEFAULT,
|
||||
};
|
||||
|
||||
const handlers = await this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions);
|
||||
|
||||
return !!(handlers && handlers.length);
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
return await this.hasHandlersForAccess(
|
||||
courseId,
|
||||
refresh,
|
||||
{ type: CoreCourseAccessDataType.ACCESS_DEFAULT },
|
||||
navOptions,
|
||||
admOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -524,6 +533,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
* @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 boolean: true if it has handlers, false otherwise.
|
||||
* @deprecated since 4.4.
|
||||
*/
|
||||
async hasHandlersForGuest(
|
||||
courseId: number,
|
||||
|
@ -531,14 +541,14 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
// Guest access.
|
||||
const accessData = {
|
||||
type: CoreCourseProvider.ACCESS_GUEST,
|
||||
};
|
||||
|
||||
const handlers = await this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions);
|
||||
|
||||
return !!(handlers && handlers.length);
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
return await this.hasHandlersForAccess(
|
||||
courseId,
|
||||
refresh,
|
||||
{ type: CoreCourseAccessDataType.ACCESS_GUEST },
|
||||
navOptions,
|
||||
admOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -547,7 +557,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
* @param courseId Course ID.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async invalidateCourseHandlers(courseId: number): Promise<void> {
|
||||
protected async invalidateCourseHandlers(courseId: number): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
const courseData = this.coursesHandlers[courseId];
|
||||
|
||||
|
@ -556,7 +566,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
}
|
||||
|
||||
courseData.enabledHandlers.forEach((handler) => {
|
||||
if (handler?.invalidateEnabledForCourse) {
|
||||
if (handler.invalidateEnabledForCourse) {
|
||||
promises.push(
|
||||
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions),
|
||||
);
|
||||
|
@ -579,7 +589,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
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.
|
||||
*/
|
||||
protected async loadCourseOptions(course: CoreCourseAnyCourseDataWithOptions, refresh = false): Promise<void> {
|
||||
if (course.navOptions === undefined || course.admOptions === undefined || refresh) {
|
||||
|
||||
const options = await CoreCourses.getCoursesAdminAndNavOptions([course.id]);
|
||||
course.navOptions = options.navOptions[course.id];
|
||||
course.admOptions = options.admOptions[course.id];
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -618,7 +629,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
||||
* @returns Resolved when updated.
|
||||
*/
|
||||
async updateHandlersForCourse(
|
||||
protected async updateHandlersForCourse(
|
||||
courseId: number,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
|
@ -676,5 +687,5 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
|
|||
export const CoreCourseOptionsDelegate = makeSingleton(CoreCourseOptionsDelegateService);
|
||||
|
||||
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.
|
||||
*/
|
||||
export enum CoreCourseModuleCompletionStatus {
|
||||
export const enum CoreCourseModuleCompletionStatus {
|
||||
COMPLETION_INCOMPLETE = 0,
|
||||
COMPLETION_COMPLETE = 1,
|
||||
COMPLETION_COMPLETE_PASS = 2,
|
||||
|
@ -90,7 +90,7 @@ export enum CoreCourseModuleCompletionStatus {
|
|||
/**
|
||||
* @deprecated since 4.3 Not used anymore.
|
||||
*/
|
||||
export enum CoreCourseCompletionMode {
|
||||
export const enum CoreCourseCompletionMode {
|
||||
FULL = 'full',
|
||||
BASIC = 'basic',
|
||||
}
|
||||
|
@ -98,12 +98,20 @@ export enum CoreCourseCompletionMode {
|
|||
/**
|
||||
* Completion tracking valid values.
|
||||
*/
|
||||
export enum CoreCourseModuleCompletionTracking {
|
||||
export const enum CoreCourseModuleCompletionTracking {
|
||||
COMPLETION_TRACKING_NONE = 0,
|
||||
COMPLETION_TRACKING_MANUAL = 1,
|
||||
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.
|
||||
*/
|
||||
|
@ -112,10 +120,17 @@ export class CoreCourseProvider {
|
|||
|
||||
static readonly ALL_SECTIONS_ID = -2;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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';
|
||||
|
||||
readonly CORE_MODULES = [
|
||||
|
|
|
@ -214,7 +214,7 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
|
|||
*/
|
||||
async getCourseUpdates(modules: CoreCourseModuleData[], courseId: number): Promise<CourseUpdates> {
|
||||
// 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();
|
||||
|
||||
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
|
||||
|
||||
Scenario: Guest access
|
||||
Given I entered the course "Course 1" as "teacher1" in the app
|
||||
And I press "Course summary" in the app
|
||||
And I press "Open in browser" in the app
|
||||
And I switch to the browser tab opened by the app
|
||||
And I log in as "teacher1"
|
||||
And I press "Actions menu"
|
||||
And I follow "More..."
|
||||
And I follow "Users"
|
||||
And I follow "Enrolment methods"
|
||||
Given I am on the "Course 1" "enrolment methods" page logged in as "teacher1"
|
||||
And I click on "Enable" "icon" in the "Guest access" "table_row"
|
||||
And I close the browser tab opened by the app
|
||||
Given I entered the app as "student2"
|
||||
And I entered the app as "student2"
|
||||
When I press "Site home" in the app
|
||||
And I press "Available courses" in the app
|
||||
And I press "Course 1" in the app
|
||||
|
|
|
@ -23,8 +23,7 @@ Feature: Test basic usage of guest access course in app
|
|||
|
||||
@lms_from4.0
|
||||
Scenario: Guest access without password (student)
|
||||
Given I log in as "teacher1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
Given I am on the "Course 1" "enrolment methods" page logged in as "teacher1"
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Allow guest access | Yes |
|
||||
|
@ -47,8 +46,7 @@ Feature: Test basic usage of guest access course in app
|
|||
|
||||
@lms_from4.3
|
||||
Scenario: Guest access with password (student)
|
||||
Given I log in as "teacher1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
Given I am on the "Course 1" "enrolment methods" page logged in as "teacher1"
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Allow guest access | Yes |
|
||||
|
|
|
@ -27,8 +27,6 @@ import { AddonEnrolSelf } from '@addons/enrol/self/services/self';
|
|||
import { CoreEnrol, CoreEnrolEnrolmentInfo, CoreEnrolEnrolmentMethod } from '@features/enrol/services/enrol';
|
||||
import { CoreSiteWSPreSets, WSObservable } from '@classes/sites/authenticated-site';
|
||||
|
||||
const ROOT_CACHE_KEY = 'mmCourses:';
|
||||
|
||||
declare module '@singletons/events' {
|
||||
|
||||
/**
|
||||
|
@ -50,6 +48,8 @@ declare module '@singletons/events' {
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreCoursesProvider {
|
||||
|
||||
protected static readonly ROOT_CACHE_KEY = 'mmCourses:';
|
||||
|
||||
static readonly SEARCH_PER_PAGE = 20;
|
||||
static readonly RECENT_PER_PAGE = 10;
|
||||
static readonly ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
|
||||
|
@ -114,7 +114,7 @@ export class CoreCoursesProvider {
|
|||
* @returns Cache key.
|
||||
*/
|
||||
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) {
|
||||
// Only 1 course, check if it belongs to the user courses. If so, use all user courses.
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||
return firstValueFrom(this.getUserAdministrationOptionsObservable(courseIds, { siteId }));
|
||||
getUserAdministrationOptions(
|
||||
courseIds: number[],
|
||||
options?: CoreSitesCommonWSOptions,
|
||||
): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||
return firstValueFrom(this.getUserAdministrationOptionsObservable(courseIds, options));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -752,7 +755,7 @@ export class CoreCoursesProvider {
|
|||
* @returns Cache key.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
async getUserNavigationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||
return firstValueFrom(this.getUserNavigationOptionsObservable(courseIds, { siteId }));
|
||||
getUserNavigationOptions(
|
||||
courseIds: number[],
|
||||
options?: CoreSitesCommonWSOptions,
|
||||
): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
|
||||
return firstValueFrom(this.getUserNavigationOptionsObservable(courseIds, options));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -981,7 +987,7 @@ export class CoreCoursesProvider {
|
|||
* @returns Cache key.
|
||||
*/
|
||||
protected getUserCoursesCacheKey(): string {
|
||||
return ROOT_CACHE_KEY + 'usercourses';
|
||||
return `${CoreCoursesProvider.ROOT_CACHE_KEY}usercourses`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
CoreCourseSearchedData,
|
||||
CoreCourseUserAdminOrNavOptionIndexed,
|
||||
} from '@features/courses/services/courses';
|
||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreCourse, CoreCourseAccessDataType } from '@features/course/services/course';
|
||||
import {
|
||||
CoreGrades,
|
||||
CoreGradesGradeItem,
|
||||
|
@ -680,11 +680,11 @@ export class CoreGradesHelperProvider {
|
|||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
||||
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||
return false; // Not enabled for guests.
|
||||
}
|
||||
|
||||
if (navOptions && navOptions.grades !== undefined) {
|
||||
if (navOptions?.grades !== undefined) {
|
||||
return navOptions.grades;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
|||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||
|
||||
if (navOptions && navOptions.grades !== undefined) {
|
||||
if (navOptions?.grades !== undefined) {
|
||||
// No need to invalidate user courses.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ export class CoreGradesCourseParticipantsOptionHandlerService implements CoreCou
|
|||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||
|
||||
if (navOptions && navOptions.grades !== undefined) {
|
||||
if (navOptions?.grades !== undefined) {
|
||||
// No need to invalidate user courses.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ export class CoreH5PCore {
|
|||
toHash.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
// Calculate hash.
|
||||
return <string> Md5.hashAsciiStr(toHash.join(''));
|
||||
return Md5.hashAsciiStr(toHash.join(''));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1104,14 +1104,14 @@ export class CoreLoginHelperProvider {
|
|||
|
||||
// Validate the signature.
|
||||
// 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 (launchSiteURL.indexOf('https://') != -1) {
|
||||
launchSiteURL = launchSiteURL.replace('https://', 'http://');
|
||||
} else {
|
||||
launchSiteURL = launchSiteURL.replace('http://', 'https://');
|
||||
}
|
||||
signature = <string> Md5.hashAsciiStr(launchSiteURL + passport);
|
||||
signature = Md5.hashAsciiStr(launchSiteURL + passport);
|
||||
}
|
||||
|
||||
if (signature == params[0]) {
|
||||
|
|
|
@ -123,7 +123,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
const hash = <string> Md5.hashAsciiStr(path);
|
||||
const hash = Md5.hashAsciiStr(path);
|
||||
|
||||
CoreNavigator.navigate(`../${hash}`, {
|
||||
params: {
|
||||
|
|
|
@ -151,7 +151,7 @@ export class CoreSharedFilesProvider {
|
|||
* @returns File ID.
|
||||
*/
|
||||
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 = {
|
||||
courseid: course.id,
|
||||
};
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||
|
||||
return {
|
||||
title: this.title,
|
||||
|
|
|
@ -24,13 +24,13 @@ import {
|
|||
CoreSitePluginsContent,
|
||||
CoreSitePluginsCourseModuleHandlerData,
|
||||
CoreSitePluginsPlugin,
|
||||
CoreSitePluginsProvider,
|
||||
} from '@features/siteplugins/services/siteplugins';
|
||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreSitePluginsBaseHandler } from './base-handler';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
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.
|
||||
|
@ -114,7 +114,7 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
|
|||
this.loadCoursePageTemplate(module, courseId, handlerData, method);
|
||||
|
||||
// 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) {
|
||||
this.loadCoursePageTemplate(module, courseId, handlerData, method, !data.alreadyFetched);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
|
|||
courseid: contextId,
|
||||
userid: user.id,
|
||||
};
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`siteplugins/content/${this.plugin.component}/${this.handlerSchema.method}/${hash}`,
|
||||
|
|
|
@ -58,7 +58,7 @@ export class CoreSitePluginsOnlyTitleBlockComponent extends CoreBlockBaseCompone
|
|||
contextlevel: this.contextLevel,
|
||||
instanceid: this.instanceId,
|
||||
};
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
||||
const hash = Md5.hashAsciiStr(JSON.stringify(args));
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`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 { 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 { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '@features/siteplugins/constants';
|
||||
|
||||
/**
|
||||
* Component to render a site plugin content.
|
||||
|
@ -181,7 +182,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
|
||||
component = component || this.component;
|
||||
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}`, {
|
||||
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).
|
||||
*/
|
||||
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 {
|
||||
const component = this.component || this.parentContent?.component;
|
||||
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}`, {
|
||||
params: {
|
||||
|
|
|
@ -98,7 +98,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit {
|
|||
} else {
|
||||
const component = this.component || this.parentContent?.component;
|
||||
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}`, {
|
||||
params: {
|
||||
|
|
|
@ -268,13 +268,13 @@ export class CoreSitePluginsHelperProvider {
|
|||
}
|
||||
|
||||
// Create a "fake" instance to hold all the libraries.
|
||||
const lazyLibraries = await CoreCompile.getLazyLibraries();
|
||||
const instance = {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
HANDLER_DISABLED: HANDLER_DISABLED,
|
||||
};
|
||||
|
||||
CoreCompile.injectLibraries(instance, lazyLibraries);
|
||||
await CoreCompile.loadLibraries();
|
||||
CoreCompile.injectLibraries(instance);
|
||||
|
||||
// Add some data of the WS call 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 { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
||||
import { CoreUserProfileHandlerType } from '@features/user/services/user-delegate';
|
||||
import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '../constants';
|
||||
|
||||
const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
||||
|
||||
|
@ -44,7 +45,7 @@ const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
|||
export class CoreSitePluginsProvider {
|
||||
|
||||
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 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
|
||||
*/
|
||||
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 { 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 = [
|
||||
{
|
||||
path: 'siteplugins/content/:component/:method/:hash',
|
||||
|
|
|
@ -218,7 +218,7 @@ export class CoreStylesService {
|
|||
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.
|
||||
if (this.stylesEls[siteId][handler.name] === hash) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseProvider } from '@features/course/services/course';
|
||||
import { CoreCourseAccessDataType } from '@features/course/services/course';
|
||||
import {
|
||||
CoreCourseAccess,
|
||||
CoreCourseOptionsHandler,
|
||||
|
@ -37,7 +37,7 @@ export class CoreUserCourseOptionHandlerService implements CoreCourseOptionsHand
|
|||
* @inheritdoc
|
||||
*/
|
||||
invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||
if (navOptions && navOptions.participants !== undefined) {
|
||||
if (navOptions?.participants !== undefined) {
|
||||
// No need to invalidate anything.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -60,11 +60,11 @@ export class CoreUserCourseOptionHandlerService implements CoreCourseOptionsHand
|
|||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): boolean | Promise<boolean> {
|
||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
||||
if (accessData && accessData.type === CoreCourseAccessDataType.ACCESS_GUEST) {
|
||||
return false; // Not enabled for guests.
|
||||
}
|
||||
|
||||
if (navOptions && navOptions.participants !== undefined) {
|
||||
if (navOptions?.participants !== undefined) {
|
||||
return navOptions.participants;
|
||||
}
|
||||
|
||||
|
|
|
@ -1303,7 +1303,7 @@ export class CoreFilepoolProvider {
|
|||
* @returns File download ID.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
|
||||
// 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]
|
||||
? this.displayedModals[modalId]
|
||||
|
|
|
@ -4,22 +4,22 @@ Feature: It opens external links properly.
|
|||
Background:
|
||||
Given the following "users" exist:
|
||||
| username |
|
||||
| student1 |
|
||||
| teacher1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | teacher |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| forum | Test forum | Test forum | C1 | forum |
|
||||
And the following forum discussions exist in course "Course 1":
|
||||
| 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
|
||||
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
|
||||
And I press "moodle.org external link" in the app
|
||||
Then I should find "You are about to leave the app" in the app
|
||||
|
|
Loading…
Reference in New Issue