forked from EVOgeek/Vmeda.Online
		
	
						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 }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								src/core/features/siteplugins/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/core/features/siteplugins/constants.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user