From 0af808571cf745e778a9636fc5906e53283349e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 25 Jan 2022 17:33:20 +0100 Subject: [PATCH] MOBILE-3915 course: Improve Course index page --- scripts/langindex.json | 7 +- .../components/course-index/course-index.html | 79 ++++++++----- .../components/course-index/course-index.scss | 24 +++- .../components/course-index/course-index.ts | 111 +++++++++++++----- .../components/format/core-course-format.html | 3 +- .../course/components/format/format.scss | 6 + .../course/components/format/format.ts | 23 +++- .../handlers/singleactivity-format.ts | 2 +- .../weeks/services/handlers/weeks-format.ts | 21 ++-- src/core/features/course/lang.json | 13 +- .../course/services/format-delegate.ts | 48 +++++++- .../services/handlers/default-format.ts | 10 +- .../classes/handlers/course-format-handler.ts | 5 +- .../siteplugins/services/siteplugins.ts | 4 + src/core/services/utils/dom.ts | 2 +- upgrade.txt | 1 + 16 files changed, 264 insertions(+), 95 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index c2d7b6f89..70e2ef078 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1536,19 +1536,23 @@ "core.course.confirmpartialdownloadsize": "local_moodlemobileapp", "core.course.couldnotloadsectioncontent": "local_moodlemobileapp", "core.course.couldnotloadsections": "local_moodlemobileapp", + "core.course.courseindex": "courseformat", "core.course.coursesummary": "moodle", + "core.course.done": "completion", "core.course.downloadcourse": "tool_mobile", "core.course.downloadcoursesprogressdescription": "local_moodlemobileapp", "core.course.downloadsectionprogressdescription": "local_moodlemobileapp", "core.course.errordownloadingcourse": "local_moodlemobileapp", "core.course.errordownloadingsection": "local_moodlemobileapp", "core.course.errorgetmodule": "local_moodlemobileapp", + "core.course.failed": "completion", "core.course.gotonextactivity": "local_moodlemobileapp", "core.course.gotonextactivitynotfound": "local_moodlemobileapp", "core.course.gotopreviousactivity": "local_moodlemobileapp", "core.course.gotopreviousactivitynotfound": "local_moodlemobileapp", "core.course.hiddenfromstudents": "moodle", "core.course.hiddenoncoursepage": "moodle", + "core.course.highlighted": "moodle", "core.course.insufficientavailablequota": "local_moodlemobileapp", "core.course.insufficientavailablespace": "local_moodlemobileapp", "core.course.manualcompletionnotsynced": "local_moodlemobileapp", @@ -1557,7 +1561,8 @@ "core.course.overriddennotice": "grades", "core.course.refreshcourse": "local_moodlemobileapp", "core.course.section": "moodle", - "core.course.sections": "moodle", + "core.course.thisweek": "format_weeks/currentsection", + "core.course.todo": "completion", "core.course.useactivityonbrowser": "local_moodlemobileapp", "core.course.warningmanualcompletionmodified": "local_moodlemobileapp", "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp", diff --git a/src/core/features/course/components/course-index/course-index.html b/src/core/features/course/components/course-index/course-index.html index 242cf97c4..7a97a6b41 100644 --- a/src/core/features/course/components/course-index/course-index.html +++ b/src/core/features/course/components/course-index/course-index.html @@ -13,49 +13,66 @@ - - - +

- - - - - {{ 'core.course.hiddenfromstudents' | translate }} - - - {{ 'core.notavailable' | translate }} - - - - -
-
- - - - - - - - + + + + +

- +

+ {{highlighted}} +
+
+ + + + + + + + + + + +

+ + +

+
+ +
+
+
+
diff --git a/src/core/features/course/components/course-index/course-index.scss b/src/core/features/course/components/course-index/course-index.scss index 828f42727..8ef120471 100644 --- a/src/core/features/course/components/course-index/course-index.scss +++ b/src/core/features/course/components/course-index/course-index.scss @@ -2,6 +2,7 @@ core-progress-bar { --bar-margin: 8px 0 4px 0; --line-height: 20px; + --background: var(--contrast-background); } @if ($core-hide-progress-on-section-selector) { @@ -10,6 +11,25 @@ core-progress-bar { } } -ion-badge { - text-align: start; +ion-icon.completioninfo { + font-size: 10px; + width: 18px; +} + +ion-item.section::part(native) { + --padding-start: 0; +} + +ion-icon.expandable-status-icon { + margin: 0; + @include padding(12px, 32px, 12px, 16px); +} + +ion-item.item-current ion-icon.expandable-status-icon { + @include padding(null, null, null, 11px); + +} + +ion-icon.restricted { + font-size: 14px; } diff --git a/src/core/features/course/components/course-index/course-index.ts b/src/core/features/course/components/course-index/course-index.ts index 3b51cad30..87c653fb2 100644 --- a/src/core/features/course/components/course-index/course-index.ts +++ b/src/core/features/course/components/course-index/course-index.ts @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit } from '@angular/core'; +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; -import { CoreCourseModuleData, CoreCourseSection, CoreCourseSectionWithStatus } from '@features/course/services/course-helper'; +import { CoreCourseModuleData, CoreCourseSectionWithStatus } from '@features/course/services/course-helper'; import { CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking, @@ -23,6 +23,9 @@ import { import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreUtils } from '@services/utils/utils'; import { ModalController } from '@singletons'; +import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; +import { IonContent } from '@ionic/angular'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Component to display course index modal. @@ -34,19 +37,30 @@ import { ModalController } from '@singletons'; }) export class CoreCourseCourseIndexComponent implements OnInit { - @Input() sections?: SectionWithProgress[]; - @Input() selected?: CoreCourseSection; + @ViewChild(IonContent) content?: IonContent; + + @Input() sections?: CourseIndexSection[]; + @Input() selectedId?: number; @Input() course?: CoreCourseAnyCourseData; stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID; + allSectionId = CoreCourseProvider.ALL_SECTIONS_ID; + highlighted?: string; + + constructor( + protected elementRef: ElementRef, + ) { + } /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { if (!this.course || !this.sections || !this.course.enablecompletion || !('courseformatoptions' in this.course) || !this.course.courseformatoptions) { + this.closeModal(); + return; } @@ -55,32 +69,48 @@ export class CoreCourseCourseIndexComponent implements OnInit { if (!formatOptions || formatOptions.completionusertracked === false) { return; } + const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections); + currentSection.highlighted = true; + if (this.selectedId === undefined) { + currentSection.expanded = true; + this.selectedId = currentSection.id; + } else { + const selectedSection = this.sections.find((section) => section.id == this.selectedId); + if (selectedSection) { + selectedSection.expanded = true; + } + } this.sections.forEach((section) => { - let complete = 0; - let total = 0; section.modules.forEach((module) => { - console.error(module); - if (!module.uservisible || module.completiondata === undefined || - module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE) { - module.completionStatus = undefined; - - return; - } - - module.completionStatus = module.completiondata.state; - - total++; - if (module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || - module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) { - complete++; - } + module.completionStatus = module.completiondata === undefined || + module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE + ? undefined + : module.completiondata.state; }); - - if (total > 0) { - section.progress = complete / total * 100; - } }); + + this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); + + setTimeout(() => { + CoreDomUtils.scrollToElementBySelector( + this.elementRef.nativeElement, + this.content, + '.item.item-current', + ); + }, 200); + } + + /** + * Toggle expand status. + * + * @param event Event object. + * @param section Section to expand / collapse. + */ + toggleExpand(event: Event, section: CourseIndexSection): void { + section.expanded = !section.expanded; + event.stopPropagation(); + event.preventDefault(); } /** @@ -93,19 +123,40 @@ export class CoreCourseCourseIndexComponent implements OnInit { /** * Select a section. * + * @param event Event. * @param section Selected section object. */ - selectSection(section: SectionWithProgress): void { + selectSection(event: Event, section: CoreCourseSectionWithStatus): void { if (section.uservisible !== false) { - ModalController.dismiss(section); + ModalController.dismiss({ event, section }); + } + } + + /** + * Select a section and open a module + * + * @param event Event. + * @param section Selected section object. + * @param module Selected module object. + */ + selectModule(event: Event,section: CoreCourseSectionWithStatus, module: CoreCourseModuleData): void { + if (module.uservisible !== false) { + ModalController.dismiss({ event, section, module }); } } } -type SectionWithProgress = Omit & { - progress?: number; +type CourseIndexSection = Omit & { + highlighted?: boolean; + expanded?: boolean; modules: (CoreCourseModuleData & { completionStatus?: CoreCourseModuleCompletionStatus; })[]; }; + +export type CoreCourseIndexSectionWithModule = { + event: Event; + section: CourseIndexSection; + module?: CoreCourseModuleData; +}; diff --git a/src/core/features/course/components/format/core-course-format.html b/src/core/features/course/components/format/core-course-format.html index 3249dad88..1b34598a3 100644 --- a/src/core/features/course/components/format/core-course-format.html +++ b/src/core/features/course/components/format/core-course-format.html @@ -66,7 +66,8 @@
- +

diff --git a/src/core/features/course/components/format/format.scss b/src/core/features/course/components/format/format.scss index f6444fb72..7c33a30cc 100644 --- a/src/core/features/course/components/format/format.scss +++ b/src/core/features/course/components/format/format.scss @@ -9,3 +9,9 @@ text-transform: none; } } + +.course-section { + ion-badge { + text-align: start; + } +} diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts index 1cdc25197..760637898 100644 --- a/src/core/features/course/components/format/format.ts +++ b/src/core/features/course/components/format/format.ts @@ -44,9 +44,11 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { IonContent, IonRefresher } from '@ionic/angular'; import { CoreUtils } from '@services/utils/utils'; -import { CoreCourseCourseIndexComponent } from '../course-index/course-index'; +import { CoreCourseCourseIndexComponent, CoreCourseIndexSectionWithModule } from '../course-index/course-index'; import { CoreBlockHelper } from '@features/block/services/block-helper'; import { CoreNavigator } from '@services/navigator'; +import { database } from 'faker'; +import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; /** * Component to display course contents using a certain format. If the format isn't found, use default one. @@ -182,7 +184,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // Course has changed, try to get the components. this.getComponents(); - this.displayCourseIndex = CoreCourseFormatDelegate.displaySectionSelector(this.course); + this.displayCourseIndex = CoreCourseFormatDelegate.displayCourseIndex(this.course); this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course); this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id); @@ -319,17 +321,28 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * Display the course index modal. */ async openCourseIndex(): Promise { - const data = await CoreDomUtils.openModal({ + const data = await CoreDomUtils.openModal({ component: CoreCourseCourseIndexComponent, componentProps: { course: this.course, sections: this.sections, - selected: this.selectedSection, + selectedId: this.selectedSection?.id, }, }); if (data) { - this.sectionChanged(data); + this.sectionChanged(data.section); + if (data.module) { + if (!data.module.handlerData) { + data.module.handlerData = + await CoreCourseModuleDelegate.getModuleDataFor(data.module.modname, data.module, this.course.id); + } + + if (data.module.uservisible !== false && data.module.handlerData?.action) { + data.module.handlerData.action(data.event, data.module, data.module.course); + } + this.moduleId = data.module.id; + } } } diff --git a/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts index 945b99e4c..8e4b1f6d3 100644 --- a/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts +++ b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts @@ -71,7 +71,7 @@ export class CoreCourseFormatSingleActivityHandlerService implements CoreCourseF /** * @inheritdoc */ - displaySectionSelector(): boolean { + displayCourseIndex(): boolean { return false; } diff --git a/src/core/features/course/format/weeks/services/handlers/weeks-format.ts b/src/core/features/course/format/weeks/services/handlers/weeks-format.ts index 10de07200..3af676006 100644 --- a/src/core/features/course/format/weeks/services/handlers/weeks-format.ts +++ b/src/core/features/course/format/weeks/services/handlers/weeks-format.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreCourseFormatHandler } from '@features/course/services/format-delegate'; -import { makeSingleton } from '@singletons'; +import { makeSingleton, Translate } from '@singletons'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreCourseWSSection } from '@features/course/services/course'; import { CoreConstants } from '@/core/constants'; @@ -32,20 +32,14 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand format = 'weeks'; /** - * Whether or not the handler is enabled on a site level. - * - * @return True or promise resolved with true if enabled. + * @inheritdoc */ async isEnabled(): Promise { return true; } /** - * Given a list of sections, get the "current" section that should be displayed first. - * - * @param course The course to get the title. - * @param sections List of sections. - * @return Current section (or promise resolved with current section). + * @inheritdoc */ async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise { const now = CoreTimeUtils.timestamp(); @@ -71,6 +65,13 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand return sections[0]; } + /** + * @inheritdoc + */ + getSectionHightlightedName(): string { + return Translate.instant('core.course.thisweek'); + } + /** * Return the start and end date of a section. * @@ -83,7 +84,7 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand startDate = startDate + 7200; const dates = { - start: startDate + (CoreConstants.SECONDS_WEEK * (section.section! - 1)), + start: startDate + (CoreConstants.SECONDS_WEEK * ((section.section || 0) - 1)), end: 0, }; dates.end = dates.start + CoreConstants.SECONDS_WEEK; diff --git a/src/core/features/course/lang.json b/src/core/features/course/lang.json index d83be79fa..b3b0f24be 100644 --- a/src/core/features/course/lang.json +++ b/src/core/features/course/lang.json @@ -27,21 +27,24 @@ "confirmpartialdownloadsize": "You are about to download at least {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmlimiteddownload": "You are not currently connected to Wi-Fi. ", "courseindex": "Course index", - "gotonextactivity": "Continue to next activity", - "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.", - "gotopreviousactivity": "Continue to previous activity", - "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.", "couldnotloadsectioncontent": "Could not load the section content. Please try again later.", "couldnotloadsections": "Could not load the sections. Please try again later.", "coursesummary": "Course summary", + "done": "Done", "downloadcourse": "Download course", "downloadcoursesprogressdescription": "Downloading courses: downloaded {{count}} out of {{total}}.", "downloadsectionprogressdescription": "Downloading section: downloaded {{count}} out of {{total}}.", "errordownloadingcourse": "Error downloading course.", "errordownloadingsection": "Error downloading section.", "errorgetmodule": "Error getting activity data.", + "failed": "Failed", + "gotonextactivity": "Continue to next activity", + "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.", + "gotopreviousactivity": "Continue to previous activity", + "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.", "hiddenfromstudents": "Hidden from students", "hiddenoncoursepage": "Available but not shown on course page", + "highlighted": "Highlighted", "insufficientavailablespace": "You are trying to download {{size}}. This will leave your device with insufficient space to operate normally. Please clear some storage space first.", "insufficientavailablequota": "Your device could not allocate space to save this download. It may be reserving space for app and system updates. Please clear some storage space first.", "manualcompletionnotsynced": "Manual completion not synchronised.", @@ -50,6 +53,8 @@ "overriddennotice": "Your final grade from this activity was manually adjusted.", "refreshcourse": "Refresh course", "section": "Section", + "thisweek": "This week", + "todo": "To do", "useactivityonbrowser": "You can still use it using your device's web browser.", "warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.", "warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}" diff --git a/src/core/features/course/services/format-delegate.ts b/src/core/features/course/services/format-delegate.ts index 4bfb14bc7..94a4bcc4c 100644 --- a/src/core/features/course/services/format-delegate.ts +++ b/src/core/features/course/services/format-delegate.ts @@ -66,13 +66,22 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { */ displayEnableDownload?(course: CoreCourseAnyCourseData): boolean; + /** + * Whether the default course index should be displayed. Defaults to true. + * + * @deprecated on 4.0. Please use displayCourseIndex instead. + * @param course The course to check. + * @return Whether the default course index should be displayed. + */ + displaySectionSelector?(course: CoreCourseAnyCourseData): boolean; + /** * Whether the default section selector should be displayed. Defaults to true. * * @param course The course to check. * @return Whether the default section selector should be displayed. */ - displaySectionSelector?(course: CoreCourseAnyCourseData): boolean; + displayCourseIndex?(course: CoreCourseAnyCourseData): boolean; /** * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, @@ -93,6 +102,13 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { */ getCurrentSection?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise; + /** + * Returns the name for the highlighted section. + * + * @return The name for the highlighted section based on the given course format. + */ + getSectionHightlightedName?(): string; + /** * Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened. * Implement it only if you want to create your own page to display the course. In general it's better to use the method @@ -209,12 +225,19 @@ export class CoreCourseFormatDelegateService extends CoreDelegate(course.format || '', 'displayCourseIndex', [course]); + + if (display !== undefined) { + return display; + } + + // Use displaySectionSelector while is not completely deprecated. return !!this.executeFunctionOnEnabled(course.format || '', 'displaySectionSelector', [course]); } @@ -278,9 +301,9 @@ export class CoreCourseFormatDelegateService extends CoreDelegate { + async getCurrentSection(course: CoreCourseAnyCourseData, sections: T[]): Promise { try { - const section = await this.executeFunctionOnEnabled( + const section = await this.executeFunctionOnEnabled( course.format || '', 'getCurrentSection', [course, sections], @@ -293,6 +316,19 @@ export class CoreCourseFormatDelegateService extends CoreDelegate( + course.format || '', + 'getSectionHightlightedName', + ); + } + /** * Get the component to use to display a single section. This component will only be used if the user is viewing * a single section. If all the sections are displayed at once then it won't be used. diff --git a/src/core/features/course/services/handlers/default-format.ts b/src/core/features/course/services/handlers/default-format.ts index d7254f194..38c68d3df 100644 --- a/src/core/features/course/services/handlers/default-format.ts +++ b/src/core/features/course/services/handlers/default-format.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses'; import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; import { CoreCourseSection } from '../course-helper'; import { CoreCourseFormatHandler } from '../format-delegate'; @@ -65,7 +66,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * @inheritdoc */ - displaySectionSelector(): boolean { + displayCourseIndex(): boolean { return true; } @@ -106,6 +107,13 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { return sections[0]; } + /** + * @inheritdoc + */ + getSectionHightlightedName(): string { + return Translate.instant('core.course.highlighted'); + } + /** * @inheritdoc */ diff --git a/src/core/features/siteplugins/classes/handlers/course-format-handler.ts b/src/core/features/siteplugins/classes/handlers/course-format-handler.ts index 89cfaff07..c6b7914d8 100644 --- a/src/core/features/siteplugins/classes/handlers/course-format-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/course-format-handler.ts @@ -38,8 +38,9 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl /** * @inheritdoc */ - displaySectionSelector(): boolean { - return this.handlerSchema.displaysectionselector ?? true; + displayCourseIndex(): boolean { + // Use displaysectionselector while is not completely deprecated. + return this.handlerSchema.displaycourseindex ?? this.handlerSchema.displaysectionselector ?? true; } /** diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts index 70e85ea13..eded0cd07 100644 --- a/src/core/features/siteplugins/services/siteplugins.ts +++ b/src/core/features/siteplugins/services/siteplugins.ts @@ -884,7 +884,11 @@ export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommo export type CoreSitePluginsCourseFormatHandlerData = CoreSitePluginsHandlerCommonData & { canviewallsections?: boolean; displayenabledownload?: boolean; + /** + * @deprecated on 4.0, use displaycourseindex instead. + */ displaysectionselector?: boolean; + displaycourseindex?: boolean; }; /** diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 945871287..0dbd3e638 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1118,7 +1118,7 @@ export class CoreDomUtilsProvider { content.scrollToPoint(position[0], position[1], duration || 0); return true; - } catch (error) { + } catch { return false; } } diff --git a/upgrade.txt b/upgrade.txt index e55234e0d..8b8313511 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -16,6 +16,7 @@ information provided here is intended especially for developers. The user handler function isEnabledForCourse is now called isEnabledForContext and receives a context + contextId instead of a courseId. Some user handler's functions have also changed to accept context + contextId instead of a courseId: isEnabledForUser, getDisplayData, action. - CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page. +- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead. === 3.9.5 ===